Examples

Player System

Complete player data management system with async operations

This example uses async operations with callbacks to prevent server lag. This is the recommended approach for production servers.

Setup

require("mongo")

local client = MongoDB.Client("mongodb://localhost:27017")
local db = client:Database("gameserver")
local playersDB = db:Collection("players")

-- Create indexes (sync is fine for initialization)
playersDB:CreateIndex({ steamid = 1 }, true, "steamid_unique")
playersDB:CreateIndex({ last_seen = -1 }, false, "last_seen_desc")

Load Player

hook.Add("PlayerInitialSpawn", "LoadPlayer", function(ply)
    local steamid = ply:SteamID()
    local nickname = ply:Nick()
    
    -- Use async to prevent server lag on player join
    playersDB:FindOneAsync({ steamid = steamid }, function(err, data)
        if err then
            ErrorNoHalt("Failed to load player " .. nickname .. ": " .. err .. "\n")
            ply:Kick("Database error, please rejoin")
            return
        end
        
        if not data then
            -- New player - create default data
            playersDB:InsertOneAsync({
                steamid = steamid,
                username = nickname,
                first_join = os.time(),
                last_seen = os.time(),
                playtime = 0,
                level = 1,
                credits = 1000,
                inventory = {},
                stats = { kills = 0, deaths = 0 }
            }, function(err, id)
                if err then
                    ErrorNoHalt("Failed to create player " .. nickname .. ": " .. err .. "\n")
                else
                    print("✓ Created new player: " .. nickname)
                    ply:SetNWInt("Credits", 1000)
                    ply:SetNWInt("Level", 1)
                end
            end)
        else
            -- Existing player - load their data
            ply:SetNWInt("Credits", data.credits or 0)
            ply:SetNWInt("Level", data.level or 1)
            ply:SetNWInt("Playtime", data.playtime or 0)
            
            print("✓ Loaded player: " .. nickname)
            
            -- Update last seen
            playersDB:UpdateOneAsync(
                { steamid = steamid },
                { 
                    ["$set"] = { 
                        last_seen = os.time(),
                        username = nickname
                    }
                },
                false,
                function(err, count)
                    if err then
                        ErrorNoHalt("Failed to update last_seen for " .. nickname .. ": " .. err .. "\n")
                    end
                end
            )
        end
    end)
end)

Save on Disconnect

hook.Add("PlayerDisconnected", "SavePlayer", function(ply)
    local steamid = ply:SteamID()
    local nickname = ply:Nick()
    
    -- Save player data asynchronously
    playersDB:UpdateOneAsync(
        { steamid = steamid },
        {
            ["$set"] = {
                username = nickname,
                last_seen = os.time(),
                credits = ply:GetNWInt("Credits", 0),
                level = ply:GetNWInt("Level", 1)
            },
            ["$inc"] = {
                playtime = ply:GetNWInt("SessionPlaytime", 0)
            }
        },
        true, -- upsert
        function(err, count)
            if err then
                ErrorNoHalt("Failed to save player " .. nickname .. ": " .. err .. "\n")
            else
                print("✓ Saved player data for " .. nickname)
            end
        end
    )
end)

Track Playtime

-- Track session playtime
hook.Add("PlayerInitialSpawn", "TrackPlaytime", function(ply)
    ply:SetNWInt("SessionPlaytime", 0)
end)

hook.Add("Think", "IncrementPlaytime", function()
    for _, ply in ipairs(player.GetAll()) do
        if IsValid(ply) and ply:Alive() then
            local current = ply:GetNWInt("SessionPlaytime", 0)
            ply:SetNWInt("SessionPlaytime", current + 1)
        end
    end
end)

-- Auto-save every 5 minutes
timer.Create("AutoSavePlaytime", 300, 0, function()
    for _, ply in ipairs(player.GetAll()) do
        if not IsValid(ply) then continue end
        
        local sessionTime = ply:GetNWInt("SessionPlaytime", 0)
        if sessionTime > 0 then
            playersDB:UpdateOneAsync(
                { steamid = ply:SteamID() },
                { ["$inc"] = { playtime = sessionTime } },
                false,
                function(err, count)
                    if not err then
                        ply:SetNWInt("SessionPlaytime", 0)
                    end
                end
            )
        end
    end
end)

Add Credits

function AddCredits(ply, amount)
    if not IsValid(ply) or amount <= 0 then return end
    
    local steamid = ply:SteamID()
    
    playersDB:UpdateOneAsync(
        { steamid = steamid },
        { ["$inc"] = { credits = amount } },
        false,
        function(err, count)
            if err then
                ErrorNoHalt("Failed to add credits: " .. err .. "\n")
                ply:ChatPrint("Error adding credits!")
            elseif count > 0 then
                local newCredits = ply:GetNWInt("Credits", 0) + amount
                ply:SetNWInt("Credits", newCredits)
                ply:ChatPrint("You received " .. amount .. " credits!")
            end
        end
    )
end

Get Statistics

concommand.Add("stats", function(ply)
    if not IsValid(ply) then return end
    
    playersDB:FindOneAsync({ steamid = ply:SteamID() }, function(err, data)
        if err then
            ply:ChatPrint("Error loading stats!")
            return
        end
        
        if data then
            ply:ChatPrint("=== Your Stats ===")
            ply:ChatPrint("Level: " .. (data.level or 1))
            ply:ChatPrint("Credits: " .. (data.credits or 0))
            ply:ChatPrint("Playtime: " .. math.floor((data.playtime or 0) / 60) .. " hours")
            ply:ChatPrint("K/D: " .. (data.stats.kills or 0) .. "/" .. (data.stats.deaths or 0))
        else
            ply:ChatPrint("No stats found!")
        end
    end)
end)

Level Up System

function GiveExperience(ply, xp)
    if not IsValid(ply) or xp <= 0 then return end
    
    playersDB:FindOneAsync({ steamid = ply:SteamID() }, function(err, data)
        if err or not data then return end
        
        local currentXP = data.experience or 0
        local currentLevel = data.level or 1
        local newXP = currentXP + xp
        local xpNeeded = currentLevel * 100
        
        if newXP >= xpNeeded then
            -- Level up!
            local newLevel = currentLevel + 1
            
            playersDB:UpdateOneAsync(
                { steamid = ply:SteamID() },
                {
                    ["$set"] = {
                        level = newLevel,
                        experience = newXP - xpNeeded
                    },
                    ["$inc"] = { credits = 500 }
                },
                false,
                function(err, count)
                    if not err then
                        ply:SetNWInt("Level", newLevel)
                        ply:SetNWInt("Credits", ply:GetNWInt("Credits") + 500)
                        ply:ChatPrint("LEVEL UP! You are now level " .. newLevel)
                        ply:ChatPrint("Bonus: +500 credits")
                    end
                end
            )
        else
            -- Just add XP
            playersDB:UpdateOneAsync(
                { steamid = ply:SteamID() },
                { ["$set"] = { experience = newXP } },
                false
            )
        end
    end)
end
  • No server lag when players join/leave
  • Handles database errors gracefully
  • Non-blocking auto-save doesn't freeze the server
  • Production ready - can handle 50+ concurrent players