Example

Player Data

Load from DataStore on join, push to the client reliably, save on route request or on leave.

local DataStoreService = game:GetService("DataStoreService")
local playerStore = DataStoreService:GetDataStore("PlayerData")
local cache = {}

local function defaults()
    return { coins = 0, level = 1, xp = 0, inventory = {} }
end

game.Players.PlayerAdded:Connect(function(player)
    local ok, data = pcall(function() return playerStore:GetAsync(player.UserId) end)
    cache[player.UserId] = (ok and data) or defaults()
    app:Push(player, "player.data", cache[player.UserId])
end)

game.Players.PlayerRemoving:Connect(function(player)
    if cache[player.UserId] then
        pcall(function() playerStore:SetAsync(player.UserId, cache[player.UserId]) end)
        cache[player.UserId] = nil
    end
end)

app:Post("player/save", function(Player, Payload, req, res)
    local d = cache[Player.UserId]
    if not d then res:Status(404):Error("No data"); return end
    local ok, err = pcall(function() playerStore:SetAsync(Player.UserId, d) end)
    if ok then res:Send({ saved = true }) else res:Status(500):Error(err) end
end)

app:Get("player/data", function(Player, Payload, req, res)
    res:Send(cache[Player.UserId] or res:Status(404):Error("Not loaded"))
end)

Client

listener:Once("player.data", function(data)
    PlayerModel:Load(data)
    UI:Refresh()
end)

What this demonstrates

PatternDetail
Push on joinServer pushes data immediately after load | client doesn't have to poll
listener:OnceSubscribe exactly once, disconnect automatically after receipt
Error statusres:Status(500):Error(err) | proper HTTP-style errors

See also

Server Push  ·  Listener | Once API  ·  App  ·  MVC Pattern | clean PlayerModel architecture