This guide will walk you through your first gmsv_mongo integration, from connecting to MongoDB to performing basic operations.
Let's start with the basics. Create a new Lua file in your addon:
-- Load the MongoDB module
require("mongo")
-- Create a global DB object for your addon
DB = {}
-- Connect to MongoDB
local client = MongoDB.Client("mongodb://localhost:27017")
if not client then
error("Failed to connect to MongoDB!")
end
-- Get your database
DB.database = client:Database("my_gameserver")
-- Define your collections
DB.players = DB.database:Collection("players")
DB.logs = DB.database:Collection("logs")
print("✓ Database initialized successfully!")
Your server is now connected to MongoDB. The connection is persistent and uses connection pooling automatically.
Now let's perform basic Create, Read, Update, and Delete operations.
-- Insert a single player document
hook.Add("PlayerInitialSpawn", "SavePlayer", function(ply)
local steamid = ply:SteamID()
local name = ply:Nick()
-- Insert player data
local id = DB.players:InsertOne({
steamid = steamid,
name = name,
playtime = 0,
first_join = os.time(),
last_seen = os.time()
})
print("Saved player:", name, "with ID:", id)
end)
-- Find a specific player
local function GetPlayerData(steamid)
local playerData = DB.players:FindOne({
steamid = steamid
})
if playerData then
print("Found player:", playerData.name)
print("Playtime:", playerData.playtime, "hours")
return playerData
else
print("Player not found!")
return nil
end
end
-- Find all players with high playtime
local function GetVeteranPlayers()
local veterans = DB.players:Find({
playtime = { ["$gte"] = 100 } -- 100+ hours
}, 10) -- Limit to 10 results
print("Found", #veterans, "veteran players")
return veterans
end
-- Update player's last seen time
hook.Add("PlayerDisconnect", "UpdatePlayerData", function(ply)
local steamid = ply:SteamID()
-- Update last seen time
local updated = DB.players:UpdateOne(
{ steamid = steamid },
{
["$set"] = {
last_seen = os.time(),
name = ply:Nick() -- Update name in case they changed it
}
}
)
if updated > 0 then
print("Updated player data for", ply:Nick())
end
end)
-- Increment playtime every minute
timer.Create("UpdatePlaytime", 60, 0, function()
for _, ply in ipairs(player.GetAll()) do
DB.players:UpdateOne(
{ steamid = ply:SteamID() },
{
["$inc"] = {
playtime = 1/60 -- 1 minute in hours
}
}
)
end
end)
-- Delete a specific player
local function DeletePlayer(steamid)
local deleted = DB.players:DeleteOne({
steamid = steamid
})
if deleted > 0 then
print("Player deleted successfully")
return true
else
print("Player not found")
return false
end
end
-- Delete inactive players (not seen in 6 months)
local function CleanupInactivePlayers()
local sixMonthsAgo = os.time() - (6 * 30 * 24 * 60 * 60)
local deleted = DB.players:DeleteMany({
last_seen = { ["$lt"] = sixMonthsAgo }
})
print("Deleted", deleted, "inactive players")
return deleted
end
MongoDB is great for storing complex, nested data structures:
-- Save player inventory
local function SaveInventory(ply)
local steamid = ply:SteamID()
local inventory = {}
-- Build inventory table
for _, weapon in ipairs(ply:GetWeapons()) do
table.insert(inventory, {
class = weapon:GetClass(),
clip1 = weapon:Clip1(),
clip2 = weapon:Clip2(),
ammo = ply:GetAmmoCount(weapon:GetPrimaryAmmoType())
})
end
-- Save to database
DB.players:UpdateOne(
{ steamid = steamid },
{
["$set"] = {
inventory = inventory,
health = ply:Health(),
armor = ply:Armor(),
position = {
x = ply:GetPos().x,
y = ply:GetPos().y,
z = ply:GetPos().z,
map = game.GetMap()
}
}
},
true -- upsert: create if doesn't exist
)
end
-- Load player inventory
local function LoadInventory(ply)
local steamid = ply:SteamID()
local data = DB.players:FindOne({ steamid = steamid })
if not data or not data.inventory then
print("No saved inventory for", ply:Nick())
return
end
-- Restore inventory
ply:StripWeapons()
for _, item in ipairs(data.inventory) do
local weapon = ply:Give(item.class)
if IsValid(weapon) then
weapon:SetClip1(item.clip1)
weapon:SetClip2(item.clip2)
end
end
-- Restore stats
ply:SetHealth(data.health or 100)
ply:SetArmor(data.armor or 0)
print("Loaded inventory for", ply:Nick())
end
MongoDB supports powerful query operators:
-- Find players with specific criteria
local results = DB.players:Find({
-- Greater than or equal
playtime = { ["$gte"] = 50 },
-- Less than
level = { ["$lt"] = 10 },
-- In array
rank = { ["$in"] = { "VIP", "Admin", "Moderator" } },
-- Not equal
banned = { ["$ne"] = true },
-- Exists
email = { ["$exists"] = true },
-- Regex pattern matching
name = { ["$regex"] = "^Player" } -- Names starting with "Player"
})
Update documents with powerful operators:
-- Various update operations
DB.players:UpdateOne(
{ steamid = "STEAM_0:1:12345" },
{
-- Set field value
["$set"] = {
rank = "VIP",
vip_expires = os.time() + (30 * 24 * 60 * 60) -- 30 days
},
-- Increment numeric value
["$inc"] = {
credits = 1000,
level = 1
},
-- Push to array
["$push"] = {
achievements = "First_Kill"
},
-- Remove from array
["$pull"] = {
pending_tasks = "tutorial"
},
-- Set current timestamp
["$currentDate"] = {
last_modified = true
}
}
)
-- Count total players
local totalPlayers = DB.players:Count({})
print("Total players:", totalPlayers)
-- Count active players
local activePlayers = DB.players:Count({
last_seen = { ["$gte"] = os.time() - (7 * 24 * 60 * 60) } -- Last 7 days
})
print("Active players:", activePlayers)
-- Count VIP players
local vipCount = DB.players:Count({ rank = "VIP" })
print("VIP players:", vipCount)
For better performance when working with multiple documents:
-- Insert multiple documents at once
local function InitializeDefaultItems()
local defaultItems = {
{ name = "Health Kit", price = 50, category = "medical" },
{ name = "Armor Vest", price = 100, category = "armor" },
{ name = "Ammo Box", price = 25, category = "ammunition" },
}
local ids = DB.items:InsertMany(defaultItems)
print("Inserted", #ids, "default items")
end
-- Update multiple players at once
local function ResetDailyBonus()
local updated = DB.players:UpdateMany(
{}, -- All players
{
["$set"] = {
daily_bonus_claimed = false,
daily_bonus_date = os.date("%Y-%m-%d")
}
}
)
print("Reset daily bonus for", updated, "players")
end
Always handle potential errors gracefully:
local function SafeDatabaseOperation()
local success, result = pcall(function()
return DB.players:FindOne({ steamid = "STEAM_0:1:12345" })
end)
if success then
if result then
print("Player found:", result.name)
return result
else
print("Player not found")
return nil
end
else
print("Database error:", result)
-- Log error, notify admin, etc.
return nil
end
end
The examples above use synchronous operations for learning purposes. In production, you must use async operations to prevent server lag!
When you use sync operations, your entire server freezes while waiting for the database:
-- ❌ This freezes the server for 50-200ms!
local player = DB.players:FindOne({ steamid = ply:SteamID() })
With 20 players connecting simultaneously, that's 1-4 seconds of server freeze!
Every operation has an async version that accepts a callback:
-- Async Insert
hook.Add("PlayerInitialSpawn", "SavePlayer", function(ply)
DB.players:InsertOneAsync({
steamid = ply:SteamID(),
name = ply:Nick(),
joined = os.time()
}, function(err, id)
if err then
ErrorNoHalt("Failed to save player: " .. err .. "\n")
return
end
print("✓ Saved player with ID:", id)
end)
end)
-- Async Find
local function LoadPlayerData(ply)
DB.players:FindOneAsync({
steamid = ply:SteamID()
}, function(err, data)
if err then
ErrorNoHalt("Database error: " .. err .. "\n")
return
end
if data then
ply:SetNWInt("Money", data.money or 0)
ply:SetNWInt("Level", data.level or 1)
else
-- New player
CreateDefaultData(ply)
end
end)
end
-- Async Update
local function GiveMoney(ply, amount)
DB.players:UpdateOneAsync(
{ steamid = ply:SteamID() },
{ ["$inc"] = { money = amount } },
false, -- upsert
function(err, count)
if err then
ErrorNoHalt("Failed to update: " .. err .. "\n")
return
end
ply:ChatPrint("Received $" .. amount)
end
)
end
| Sync | Async | Callback |
|---|---|---|
InsertOne() | InsertOneAsync() | function(err, id) |
InsertMany() | InsertManyAsync() | function(err, ids) |
FindOne() | FindOneAsync() | function(err, doc) |
Find() | FindAsync() | function(err, docs) |
UpdateOne() | UpdateOneAsync() | function(err, count) |
UpdateMany() | UpdateManyAsync() | function(err, count) |
DeleteOne() | DeleteOneAsync() | function(err, count) |
DeleteMany() | DeleteManyAsync() | function(err, count) |
Count() | CountAsync() | function(err, count) |
Aggregate() | AggregateAsync() | function(err, results) |
Now that you understand the basics: