Quickstart
July 31, 2026 ยท View on GitHub
Zero to a running MongrelDB Gleam program in fifteen minutes. This guide assumes a fresh machine and walks through installing the prerequisites, starting the daemon, and writing, running, and understanding a complete program.
1. Prerequisites
You need two things installed: the Gleam toolchain (with Erlang/OTP) and a
mongreldb-server daemon.
Install Gleam 1.4.0 or newer
Verify it:
gleam --version
# gleam 1.4.0 ...
If you do not have it, install from https://gleam.run/getting-started/installing-gleam/ or your package manager.
Install mongreldb-server
Fetch a prebuilt server binary from the MongrelDB releases:
mkdir -p bin
curl -fsSL -o bin/mongreldb-server \
https://github.com/visorcraft/MongrelDB/releases/download/v0.63.1/mongreldb-server-linux-x64
chmod +x bin/mongreldb-server
Verify it runs:
./bin/mongreldb-server --version
2. Start the daemon
By default mongreldb-server listens on http://127.0.0.1:8453 and stores
data in the current working directory.
mkdir -p /tmp/mdb-data && cd /tmp/mdb-data
/path/to/mongreldb-server
In another terminal, sanity-check it:
curl http://127.0.0.1:8453/health
# ok
Leave the daemon running for the rest of this guide.
3. Create a project and pull in the client
The client is not yet published to Hex, and Gleam does not support git dependencies, so clone the repo and use a path dependency:
git clone https://github.com/visorcraft/MongrelDB-Gleam.git
gleam new my_app
cd my_app
Add the clone to your gleam.toml:
[dependencies]
mongreldb = { path = "../MongrelDB-Gleam" }
4. Write your first program
Edit src/my_app.gleam:
import gleam/int
import gleam/io
import gleam/list
import gleam/option.{None}
import mongreldb
pub fn main() {
// 1. Connect to the daemon. Empty URL falls back to http://127.0.0.1:8453.
let assert Ok(db) =
mongreldb.connect("http://127.0.0.1:8453", mongreldb.Options(token: "", username: "", password: ""))
// 2. Health check before doing anything else.
let assert Ok(True) = mongreldb.health(db)
// 3. Create a table. Each column has a stable numeric id, a name, a type,
// and optional constraint-style fields (`enum_variants`, `default_value`).
// The primary_key column is the row identity.
let assert Ok(tid) = mongreldb.create_table(db, "orders", [
mongreldb.Column(1, "id", "int64", True, False, None, None),
mongreldb.Column(2, "customer", "varchar", False, False, None, None),
mongreldb.Column(3, "amount", "float64", False, False, None, None),
// Enum column: only the four listed values are accepted.
mongreldb.Column(
4, "status", "varchar", False, False,
Some(["pending", "shipped", "delivered", "cancelled"]),
None,
),
// Enum column with a default applied when the cell is omitted.
mongreldb.Column(
5, "currency", "varchar", False, False,
Some(["USD", "EUR", "GBP"]),
Some("USD"),
),
])
io.println("created table id: " <> int.to_string(tid))
// 4. Insert rows. Cells pair column id -> value.
let assert Ok(_) = mongreldb.put(db, "orders", [
mongreldb.Cell(1, mongreldb.int_value(1)),
mongreldb.Cell(2, mongreldb.string_value("Alice")),
mongreldb.Cell(3, mongreldb.float_value(99.5)),
mongreldb.Cell(4, mongreldb.string_value("pending")),
mongreldb.Cell(5, mongreldb.string_value("USD")),
], "")
// 5. Query with a native index condition. The range index serves this in
// sub-millisecond.
let q =
db
|> mongreldb.query("orders")
|> mongreldb.where_("range", [
#("column", mongreldb.int_value(3)),
#("min", mongreldb.float_value(100.0)),
])
|> mongreldb.projection([1, 2])
|> mongreldb.limit_(100)
let assert Ok(rows) = mongreldb.execute(q)
io.println("rows: " <> int.to_string(list.length(rows)))
// 6. Count the rows.
let assert Ok(n) = mongreldb.count(db, "orders")
io.println("total rows: " <> int.to_string(n))
}
Build and run it:
gleam run
You should see:
created table id: 1
rows: 1
total rows: 2
5. What each part does
| Code | What it does |
|---|---|
mongreldb.connect(url, options) | Builds an HTTP client targeting one daemon. Backed by Erlang httpc via gleam/http. |
mongreldb.health(db) | GET /health; returns Ok(True) when the daemon answers. |
mongreldb.create_table(db, name, columns) / create_table_with_constraints(db, name, columns, constraints) | POST /kit/create_table. Column ids are the on-wire identifiers; enum_variants and default_value are optional and emitted only when set; the constraints helper forwards the native constraints object. |
mongreldb.put(db, table, cells, key) | Single-op transaction: POST /kit/txn with one put op. cells is flattened to [col_id, val, ...]. |
| `mongreldb.query(db, table) | > mongreldb.where_(...)` |
mongreldb.projection(qb, [1, 2]) | Server returns only those column ids, saving bandwidth. |
mongreldb.limit_(qb, 100) | Caps the result. |
mongreldb.execute(qb) | Sends the query and decodes the rows array. |
mongreldb.count(db, table) | GET /tables/{name}/count. |
6. Constrained columns and defaults
Column accepts two optional constraint-style fields that are forwarded to the
daemon verbatim. They are omitted from the JSON body when None, so existing
schemas that don't set them produce an identical payload.
| Field | Type | Effect |
|---|---|---|
enum_variants | Option(List(String)) | Restrict the column to one of the listed string values. The engine rejects writes outside the set with Error(Conflict). |
default_value | Option(String) | Legacy string default applied when the cell is omitted on a put. |
For typed static defaults and dynamic defaults use ColumnWithDefaults:
import gleam/json
import gleam/option.{None, Some}
// Static JSON scalar defaults preserve their JSON type.
mongreldb.ColumnWithDefaults(
id: 4,
name: "active",
ty: "bool",
primary_key: False,
nullable: True,
enum_variants: None,
default_value: None,
default_value_json: Some(json.bool(True)),
default_expr: None,
)
// Explicit JSON null is a valid static default.
mongreldb.ColumnWithDefaults(
id: 5,
name: "notes",
ty: "varchar",
primary_key: False,
nullable: True,
enum_variants: None,
default_value: None,
default_value_json: Some(json.null()),
default_expr: None,
)
// Dynamic defaults use default_expr; accepted values are "now" or "uuid".
mongreldb.ColumnWithDefaults(
id: 6,
name: "created",
ty: "varchar",
primary_key: False,
nullable: True,
enum_variants: None,
default_value: None,
default_value_json: None,
default_expr: Some("now"),
)
default_expr takes precedence server-side. A literal "now" string default
must be set through default_value_json (as json.string("now")), not
default_expr. An empty enum_variants list is also omitted, so None and
Some([]) produce identical wire shapes.
7. History retention
MongrelDB keeps a configurable number of recent commit epochs. The getters
history_retention_epochs and earliest_retained_epoch read the current
window and floor; set_history_retention_epochs changes the window. You can
query older versions with AS OF EPOCH through sql:
import gleam/int
let assert Ok(#(window, earliest)) =
mongreldb.set_history_retention_epochs(db, 10_000)
let assert Ok(window) = mongreldb.history_retention_epochs(db)
let assert Ok(earliest) = mongreldb.earliest_retained_epoch(db)
let stmt = "SELECT * FROM orders AS OF EPOCH " <> int.to_string(earliest)
let assert Ok(rows) = mongreldb.sql(db, stmt)
Lowering retention advances the earliest retained epoch; raising it again does not restore history that was already pruned.
8. Common pitfalls
Using the column name instead of the column id. Every on-wire API uses the
numeric id from create_table, never the name. The query builder's column
alias maps to the server's column_id - pass the integer id, not the string
name.
Treating a single put as non-transactional. put is a one-op
transaction. A unique constraint violation surfaces as Error(Conflict) (HTTP
409), not as a silent no-op.
Calling commit twice on the same Transaction. The second call returns
Error(AlreadyCommitted). Start a fresh mongreldb.begin(db) for each logical
unit of work.
Pointing at a daemon that requires auth. If the daemon was started with
--auth-token or --auth-users, every call returns Error(Auth) unless you
set token or username/password in Options. See auth.md.
Next steps
- transactions.md - atomic batches, idempotency, retries
- queries.md - every native index condition
- sql.md - recursive CTEs, window functions,
CREATE TABLE AS SELECT - auth.md - bearer tokens, basic auth, user/role management
- errors.md - the full typed error set and recovery patterns