CRUD Operations
Async Operations
Non-blocking database operations with callbacks
Async Operations
Async operations allow your server to continue processing while database operations complete in the background.
Why Use Async?
Synchronous (Blocking)
-- This blocks the server until complete
local result = collection:Find({}) -- Server pauses here
-- Other players experience lag
Asynchronous (Non-Blocking)
-- This returns immediately
collection:FindAsync({}, nil, function(err, result)
-- Called when complete
end)
-- Server continues running
Callback Pattern
All async operations use callbacks with this signature:
function(err, result)
if err then
-- Handle error
else
-- Process result
end
end
Available Async Methods
Insert Operations
InsertOneAsync
collection:InsertOneAsync(document, function(err, insertedId)
if err then
print("Insert error:", err)
return
end
print("Inserted with ID:", insertedId)
end)
InsertManyAsync
collection:InsertManyAsync(documents, function(err, insertedIds)
if err then
print("Insert error:", err)
return
end
print("Inserted", #insertedIds, "documents")
end)
Query Operations
FindAsync
collection:FindAsync(filter, limit, callback)
Important:
FindAsync requires a limit argument between the filter and the callback. If you omit it (or pass 0), no documents will be returned and your callback will never receive any results. Always pass the maximum number of documents you want returned, or use nil to return all matching documents (not recommended for large datasets).-- Find up to 100 documents matching a filter
collection:FindAsync({ level = { ["$gte"] = 10 } }, 100, function(err, documents)
if err then
print("Find error:", err)
return
end
print("Found", #documents, "documents")
for _, doc in ipairs(documents) do
print(" -", doc.username)
end
end)
-- Find all players (use a high limit to retrieve all documents)
collection:FindAsync({}, 1000, function(err, documents)
if err then
print("Find error:", err)
return
end
print("Total players retrieved:", #documents)
end)
FindOneAsync
collection:FindOneAsync({ steamid = steamid }, function(err, document)
if err then
print("Find error:", err)
return
end
if document then
print("Found:", document.username)
else
print("Not found")
end
end)
CountAsync
collection:CountAsync({ active = true }, function(err, count)
if err then
print("Count error:", err)
return
end
print("Active players:", count)
end)
Update Operations
UpdateOneAsync
collection:UpdateOneAsync(
{ steamid = steamid },
{ ["$inc"] = { credits = 100 } },
function(err, modifiedCount)
if err then
print("Update error:", err)
return
end
print("Modified:", modifiedCount)
end
)
UpdateManyAsync
collection:UpdateManyAsync(
{ vip = true },
{ ["$inc"] = { bonus = 50 } },
function(err, modifiedCount)
if err then
print("Update error:", err)
return
end
print("Updated", modifiedCount, "VIP players")
end
)
Delete Operations
DeleteOneAsync
collection:DeleteOneAsync(
{ steamid = steamid },
function(err, deletedCount)
if err then
print("Delete error:", err)
return
end
if deletedCount > 0 then
print("Deleted successfully")
end
end
)
DeleteManyAsync
collection:DeleteManyAsync(
{ expired = true },
function(err, deletedCount)
if err then
print("Delete error:", err)
return
end
print("Deleted", deletedCount, "expired records")
end
)
Aggregation
AggregateAsync
collection:AggregateAsync({
{
["$group"] = {
_id = "$class",
count = { ["$sum"] = 1 }
}
}
}, function(err, results)
if err then
print("Aggregation error:", err)
return
end
for _, result in ipairs(results) do
print(result._id, ":", result.count)
end
end)
Practical Examples
Async Player Loading
function LoadPlayerAsync(steamid, callback)
local players = db:Collection("players")
players:FindOneAsync({ steamid = steamid }, function(err, player)
if err then
callback(nil, err)
return
end
if player then
-- Player exists, update login time
players:UpdateOneAsync(
{ steamid = steamid },
{ ["$set"] = { last_login = os.time() } },
function() end -- Fire and forget
)
callback(player, nil)
else
-- New player, create record
local newPlayer = {
steamid = steamid,
level = 1,
credits = 1000,
created_at = os.time()
}
players:InsertOneAsync(newPlayer, function(err, id)
if err then
callback(nil, err)
else
newPlayer._id = id
callback(newPlayer, nil)
end
end)
end
end)
end
-- Usage in GMod
hook.Add("PlayerInitialSpawn", "LoadPlayerData", function(ply)
local steamid = ply:SteamID()
LoadPlayerAsync(steamid, function(player, err)
if err then
print("Failed to load player:", err)
return
end
-- Store data on player entity
ply.dbData = player
ply:SetNWInt("Level", player.level)
ply:SetNWInt("Credits", player.credits)
print("Loaded data for", ply:Nick())
end)
end)
Async Save System
function SavePlayerAsync(steamid, data, callback)
local players = db:Collection("players")
players:UpdateOneAsync(
{ steamid = steamid },
{ ["$set"] = data },
function(err, modified)
if callback then
callback(err, modified > 0)
end
end
)
end
-- Auto-save every 5 minutes
timer.Create("AutoSave", 300, 0, function()
for _, ply in ipairs(player.GetAll()) do
if ply.dbData then
SavePlayerAsync(ply:SteamID(), {
credits = ply.dbData.credits,
level = ply.dbData.level,
playtime = ply.dbData.playtime + 300
})
end
end
print("Auto-save complete")
end)
Async Leaderboard
function GetLeaderboardAsync(callback)
local players = db:Collection("players")
players:AggregateAsync({
{ ["$match"] = { banned = { ["$ne"] = true } } },
{ ["$sort"] = { score = -1 } },
{ ["$limit"] = 10 },
{ ["$project"] = {
username = 1,
score = 1,
level = 1,
_id = 0
}}
}, function(err, results)
if err then
callback(nil, err)
else
callback(results, nil)
end
end)
end
-- Usage
GetLeaderboardAsync(function(leaderboard, err)
if err then
print("Failed to get leaderboard:", err)
return
end
print("=== Top 10 Players ===")
for i, entry in ipairs(leaderboard) do
print(string.format("%d. %s - %d points",
i, entry.username, entry.score))
end
end)
Fire-and-Forget Logging
function LogEventAsync(category, message, data)
local logs = db:Collection("logs")
logs:InsertOneAsync({
category = category,
message = message,
data = data or {},
timestamp = os.time()
}, function(err)
-- Optional: log failures to console
if err then
print("[LOG ERROR]", err)
end
end)
end
-- Usage - doesn't block game
LogEventAsync("player", "Player connected", { steamid = "STEAM_0:1:12345" })
LogEventAsync("economy", "Purchase made", { item = "weapon", cost = 500 })
LogEventAsync("combat", "Player killed", { killer = "A", victim = "B" })
Error Handling Patterns
Basic Error Handling
collection:FindOneAsync({ steamid = steamid }, function(err, result)
if err then
print("Database error:", err)
-- Handle error (notify player, retry, etc.)
return
end
-- Process result
end)
Retry Pattern
function FindWithRetry(collection, filter, maxRetries, callback)
local attempts = 0
local function attempt()
attempts = attempts + 1
collection:FindOneAsync(filter, function(err, result)
if err and attempts < maxRetries then
print("Retrying... attempt", attempts + 1)
timer.Simple(1, attempt) -- Wait 1 second before retry
else
callback(err, result)
end
end)
end
attempt()
end
-- Usage
FindWithRetry(players, { steamid = steamid }, 3, function(err, player)
if err then
print("Failed after 3 attempts:", err)
else
print("Found player:", player.username)
end
end)
Callback Wrapper
function SafeCallback(callback)
return function(err, result)
local success, error = pcall(callback, err, result)
if not success then
print("Callback error:", error)
end
end
end
-- Usage
collection:FindOneAsync(filter, SafeCallback(function(err, result)
-- Your callback code
end))
Best Practices
- Use async for gameplay logic: Prevents server lag
- Handle all errors: Never ignore the error parameter
- Use fire-and-forget for logging: Non-critical operations
- Avoid callback hell: Structure code cleanly
- Consider timeouts: Handle slow operations
-- Avoid deep nesting (callback hell)
-- Bad:
collection:FindAsync(filter, 100, function(err, results)
if not err then
collection:UpdateAsync(filter2, update, function(err, result)
if not err then
collection:FindAsync(filter3, 100, function(err, results)
-- Too deep!
end)
end
end)
end
end)
-- Better: Use separate functions
local function step3(data)
collection:FindAsync(data.filter, 100, function(err, results)
-- Handle final step
end)
end
local function step2(data)
collection:UpdateAsync(data.filter, data.update, function(err, result)
if not err then step3(data) end
end)
end
local function step1(data)
collection:FindAsync(data.filter, 100, function(err, results)
if not err then step2(data) end
end)
end
Next Steps
- Query Operators - Advanced filtering
- Advanced Features - Aggregation and indexes
- Examples - Real-world async examples