Introduction

Core Concepts

Understanding the fundamentals of gmsv_mongo

Core Concepts

Understanding these core concepts will help you effectively use gmsv_mongo in your Garry's Mod server.

The Architecture

gmsv_mongo follows MongoDB's standard client-server architecture:

┌─────────────────┐
│  GMod Server    │
│  (Lua Code)     │
└────────┬────────┘
         │ require("mongo")
         ↓
┌─────────────────┐
│  gmsv_mongo     │
│  (Rust Module)  │
└────────┬────────┘
         │ TCP/IP
         ↓
┌─────────────────┐
│  MongoDB Server │
│  (Database)     │
└─────────────────┘

The Hierarchy

Data in gmsv_mongo is organized in a three-level hierarchy:

ClientDatabaseCollectionDocument

Each level serves a specific purpose in organizing and managing your data.

1. Client

The Client is your connection to the MongoDB server.

-- Create a client
local client = MongoDB.Client("mongodb://localhost:27017")

Key Points:

  • One client per MongoDB server connection
  • Handles connection pooling automatically
  • Reusable across your entire addon
  • Thread-safe and async-ready

Best Practice:

-- Create ONE global client for your addon
if not DB then
    DB = {}
    DB.client = MongoDB.Client("mongodb://localhost:27017")
end

2. Database

A Database is a container for collections. Think of it as a namespace for your data.

-- Get a database
local db = client:Database("my_gameserver")

Key Points:

  • Databases are created automatically when first accessed
  • Each addon/gamemode should use its own database
  • Use descriptive names: darkrp_main, ttt_stats, sandbox_builds

Example Structure:

MongoDB Server
├── darkrp_main          ← Database for DarkRP
│   ├── players
│   ├── jobs
│   └── transactions
├── ttt_statistics       ← Database for TTT
│   ├── rounds
│   ├── player_stats
│   └── detective_logs
└── admin_tools          ← Database for admin system
    ├── bans
    ├── warnings
    └── logs

3. Collection

A Collection is a group of related documents. Similar to a table in SQL databases, but schema-less.

-- Get a collection
local players = db:Collection("players")

Key Points:

  • Collections are created automatically when first used
  • No schema required - documents can have different fields
  • Use plural names: players, items, transactions
  • Collections are equivalent to SQL tables

Naming Conventions:

-- Good collection names
local players = db:Collection("players")
local inventory_items = db:Collection("inventory_items")
local ban_records = db:Collection("ban_records")

-- Avoid
local data = db:Collection("data")  -- Too generic
local Player = db:Collection("Player")  -- Use lowercase, plural

4. Document

A Document is a single record in a collection. It's a Lua table that gets converted to BSON.

-- A document
local playerDoc = {
    steamid = "STEAM_0:1:12345",
    name = "Player1",
    level = 5,
    inventory = {
        { item = "weapon_pistol", count = 1 },
        { item = "ammo_9mm", count = 50 }
    },
    stats = {
        kills = 150,
        deaths = 75,
        playtime = 3600
    }
}

Key Points:

  • Documents are flexible - no fixed schema
  • Support nested structures (tables within tables)
  • Automatically get a unique _id field
  • Maximum size: 16MB per document

Connection Management

Connection String Format

The connection string tells gmsv_mongo where and how to connect:

-- Basic format
"mongodb://host:port"

-- With authentication
"mongodb://username:password@host:port"

-- With database
"mongodb://username:password@host:port/database"

-- With options
"mongodb://host:port/?option1=value1&option2=value2"

Examples:

-- Local development
local client = MongoDB.Client("mongodb://localhost:27017")

-- Production with auth
local client = MongoDB.Client("mongodb://admin:SecurePass123@db.example.com:27017")

-- With SSL
local client = MongoDB.Client("mongodb://user:pass@host:27017/?ssl=true")

-- MongoDB Atlas (cloud)
local client = MongoDB.Client("mongodb+srv://user:pass@cluster.mongodb.net/mydb")

Connection Options

For advanced configuration, use ClientWithOptions:

local client = MongoDB.ClientWithOptions("mongodb://localhost:27017", {
    app_name = "MyGModServer",      -- Identifies your app in MongoDB logs
    max_pool_size = 50,              -- Maximum connections in pool
    min_pool_size = 5,               -- Minimum connections to maintain
    retry_writes = true,             -- Automatically retry failed writes
    retry_reads = true,              -- Automatically retry failed reads
    server_selection_timeout = 30,   -- Seconds to wait for server selection
    connect_timeout = 10             -- Seconds to wait for connection
})

Data Types

gmsv_mongo automatically converts between Lua and BSON types:

Lua TypeBSON TypeExample
number (int)Int32/Int64level = 5
number (float)Doublerating = 4.5
stringStringname = "Player1"
booleanBooleanactive = true
table (array)Array{1, 2, 3}
table (object)Document{name = "test"}
nilNulldata = nil

Special Types:

-- ObjectId (automatically created for _id)
local doc = players:FindOne({ steamid = "STEAM_0:1:12345" })
print(doc._id)  -- "507f1f77bcf86cd799439011"

-- Timestamps (use os.time())
{
    created_at = os.time(),          -- Unix timestamp
    updated_at = os.time()
}

-- Dates (use os.date())
{
    date_string = os.date("%Y-%m-%d")  -- "2026-01-19"
}

Working with IDs

Every document has a unique _id field:

-- Insert returns the ID
local id = players:InsertOne({ name = "Player1" })
print("Document ID:", id)  -- "507f1f77bcf86cd799439011"

-- Find by ID
local doc = players:FindOne({ _id = id })

-- Custom ID (not recommended unless necessary)
players:InsertOne({
    _id = "custom_id_123",  -- Use with caution
    name = "Player1"
})
  • Let MongoDB generate _id automatically
  • Use custom fields (like steamid) for your queries
  • Don't use _id for business logic
  • IDs are guaranteed unique within a collection

Async Operations

All gmsv_mongo operations are asynchronous under the hood:

-- Operations are non-blocking
local result = players:Find({ active = true })

-- The Lua code waits, but GMod server doesn't freeze
-- Other Lua threads continue executing

What this means:

  • Your server won't lag during database operations
  • Multiple queries can run concurrently
  • Connection pooling enables efficient multi-threading
  • No need for callbacks or coroutines

Error Handling

Database operations can fail. Always handle errors:

-- Basic error checking
local result = players:FindOne({ steamid = steamid })
if not result then
    -- Could mean not found OR error occurred
    print("Player not found or error occurred")
end

-- Detailed error handling with pcall
local success, result = pcall(function()
    return players:FindOne({ steamid = steamid })
end)

if success then
    if result then
        print("Player found!")
    else
        print("Player doesn't exist")
    end
else
    -- result contains error message
    print("Database error:", result)
    -- Log to file, notify admin, etc.
end

Performance Considerations

Use Indexes

Create indexes on frequently queried fields for 10-100x speed improvement.

Bulk Operations

Use InsertMany, UpdateMany instead of loops with single operations.

Limit Results

Always use limits on Find queries to avoid loading thousands of documents.

Project Fields

Only fetch the fields you need using aggregation pipelines.

Connection Lifecycle

-- 1. Module loads
require("mongo")

-- 2. Create client (happens once)
local client = MongoDB.Client("mongodb://localhost:27017")

-- 3. Get database reference (lightweight)
local db = client:Database("gameserver")

-- 4. Get collection reference (lightweight)
local players = db:Collection("players")

-- 5. Perform operations (async, pooled)
local result = players:Find({})

-- 6. Connection persists until server shutdown
-- No need to manually close connections