On this page
Pro Tip
Scripts run directly on the server, offering high-performance access to data without network round-trips.
Lua Scripting
Extend SoliDB with custom API endpoints written in Lua. Execute logic directly on the database server for maximum performance.
Overview
SoliDB allows you to embed Lua scripts that act as custom API endpoints. These scripts have direct access to internal database objects, allowing you to perform complex data manipulations, validations, or multi-step transactions in a single HTTP request.
Performance
Zero network latency between your logic and your data. Ideal for complex aggregates.
Simplicity
Write clean, synchronous Lua code. No async/await hell.
Custom APIs
Expose exactly the endpoints your frontend needs, hiding complex queries.
Security Sandbox
Scripts run in a strictly sandboxed isolation environment to ensure server stability and security.
Allowed Modules
- string
- table
- math
- utf8
- bit
Disabled Modules
- os (Access system commands)
- io (File system access)
- debug (Reflection tools)
- package (Module loading)
Attempts to access disabled modules or perform unauthorized system calls will result in a runtime error.
Management API
Before you can execute a script, you must register it via the management API.
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /_api/scripts | Create a new script |
| GET | /_api/scripts | List all scripts |
| PUT | /_api/scripts/:id | Update a script |
| DELETE | /_api/scripts/:id | Delete a script |
Lua API Reference
solidb
The main entry point for database operations.
-
db
The current database object.
-
solidb.log(message)
Log a message to the server console.
-
solidb.fetch(url, options) -> Response
Make an HTTP request. Options:
{ method="GET", headers={}, body="" }. Returns{ status, body, headers, ok }.
Database
-
db:collection(name) -> Collection
Get a handle to a collection.
-
db:query(query, params) -> list<document>
Execute an SDBQL query.
-
db:transaction(callback) -> any
Execute operations atomically within a transaction. The callback receives a
txcontext. Auto-commits on success, auto-rollbacks on error.
Transactions
Inside a db:transaction() callback, use the tx object for atomic operations.
-
tx:collection(name) -> TxCollection
Get a transactional collection handle.
- txCol:insert(doc) -> document
- txCol:update(key, doc) -> document
- txCol:delete(key) -> bool
- txCol:get(key) -> document | nil
Collection
- col:get(key) -> document | nil
- col:insert(doc) -> document
- col:update(key, doc) -> document
- col:delete(key) -> bool
- col:count() -> int
String Extensions
Extensions to the standard Lua string library.
-
string.regex(subject, pattern) -> bool
Check if a string matches a regex pattern. Also available as
subject:regex(pattern).
Example:string.regex("hello", "^he")->true -
string.regex_replace(subject, pattern, replacement) -> string
Replace all occurrences of a regex pattern. Also available as
subject:regex_replace(pattern, repl).
Example:string.regex_replace("a1b2", "\\d", "#")->"a#b#"
Crypto Functions
Cryptographic utilities exposed via the crypto global.
- crypto.md5(data) -> string (hex)
- crypto.sha256(data) -> string (hex)
- crypto.sha512(data) -> string (hex)
- crypto.hmac_sha256(key, data) -> string (hex)
- crypto.hmac_sha512(key, data) -> string (hex)
- crypto.base64_encode(data) -> string
- crypto.base64_decode(data) -> string
- crypto.base32_encode(data) -> string
- crypto.base32_decode(data) -> string
- crypto.uuid() -> string (v4)
- crypto.uuid_v7() -> string (v7)
- crypto.random_bytes(len) -> string (bytes)
-
crypto.curve25519(secret, public) -> string (bytes)
Calculate X25519 shared secret or public key.
crypto.curve25519(secret, "\9")-> public key - crypto.jwt_encode(claims, secret) -> string
- crypto.jwt_decode(token, secret) -> table
-
crypto.hash_password(pwd) -> string
Hash password using Argon2 (Async/Non-blocking).
-
crypto.verify_password(hash, pwd) -> bool
Verify password (Async/Non-blocking).
Time Functions
Time utilities via time global.
- time.now() -> float (seconds)
- time.now_ms() -> int (milliseconds)
- time.iso() -> string (ISO 8601)
-
time.sleep(ms) -> nil
Suspend execution for N ms (Non-blocking).
-
time.format(ts, format) -> string
Format timestamp (e.g. "%Y-%m-%d %H:%M:%S").
- time.parse(iso) -> float (seconds)
-
time.add(ts, val, unit) -> float
Add unit ("ms", "s", "m", "h", "d") to timestamp.
-
time.subtract(ts, val, unit) -> float
Subtract unit from timestamp.
request
Contains information about the incoming HTTP request.
- request.method -> string ("GET", "POST", etc.)
- request.path -> string
- request.query -> table (URL query parameters)
- request.params -> table (URL path parameters, e.g. :id)
- request.body -> table | nil (Parsed JSON body)
- request.headers -> table
Examples
1. Hello World with Query Params
-- Returns: { "greeting": "Hello, Alice!" } local name = request.query.name or "World" return { greeting = "Hello, " .. name .. "!" }
2. CRUD API for "Users"
Handle both GET (list) and POST (create) in a single script.
-- Global 'db' object is pre-connected to your database local users = db:collection("users") if request.method == "POST" then -- Validate input if not request.body.email then return { error = "Email required" } end -- Create user local doc = users:insert(request.body) return { status = "created", user = doc } else -- List users (supports simple pagination via query params) local limit = tonumber(request.query.limit) or 10 -- Execute SDBQL query for better performance local all_users = db:query("FOR u IN users RETURN u") return { data = all_users, count = users:count() } end
3. SDBQL Query with Parameters
Execute complex queries safely using bind parameters.
-- Get users older than 'min_age' query parameter local min_age = tonumber(request.query.age) or 18 -- Execute SDBQL query with bind variables (safe against injection) local results = db:query( "FOR u IN users FILTER u.age >= @age RETURN u", { age = min_age } ) return { count = #results, users = results }
4. URL Path Parameters
Handle dynamic paths like customers/:id.
-- Script path defined as: customers/:id local id = request.params.id if not id then return { error = "ID required" } end -- Look up customer by ID local customers = db:collection("customers") local customer = customers:get(id) return customer or { error = "Customer not found" }
5. HTTP Request (Webhook)
Send data to an external service using solidb.fetch.
This function is asynchronous and non-blocking — execution suspends until the response arrives, freeing up server resources.
-- Post data to an external API local res = solidb.fetch("https://api.example.com/webhook", { method = "POST", headers = { ["Content-Type"] = "application/json", ["Authorization"] = "Bearer secret" }, body = '{ "event": "user_created" }' }) return { status = res.status, upstream_response = res.body }
6. End-to-End Encryption with Curve25519
Establish a shared secret between two parties using Elliptic Curve Diffie-Hellman.
-- 1. Alice generates her keypair local alice_secret = crypto.random_bytes(32) local alice_public = crypto.curve25519(alice_secret, "\9") -- Generate public key -- 2. Bob generates his keypair local bob_secret = crypto.random_bytes(32) local bob_public = crypto.curve25519(bob_secret, "\9") -- 3. Calculate shared secret (Alice uses Bob's public key, Bob uses Alice's) local shared_secret_alice = crypto.curve25519(alice_secret, bob_public) local shared_secret_bob = crypto.curve25519(bob_secret, alice_public) -- The shared secrets are identical local keys_match = (shared_secret_alice == shared_secret_bob) -- Derive a session key (e.g. for HMAC or encryption) local session_key = crypto.sha256(shared_secret_alice) return { alice_public_hex = crypto.hex_encode(alice_public), bob_public_hex = crypto.hex_encode(bob_public), keys_match = keys_match, session_key_hex = session_key }
7. Atomic Transactions
Perform multiple operations atomically with auto-commit on success or auto-rollback on error.
-- Transfer credits between two accounts atomically db:transaction(function(tx) local accounts = tx:collection("accounts") -- Debit source account local source = accounts:get("alice") accounts:update("alice", { balance = source.balance - 100 }) -- Credit destination account local dest = accounts:get("bob") accounts:update("bob", { balance = dest.balance + 100 }) -- If any error occurs, all changes are rolled back end) return { status = "Transfer complete!" }