Getting Started · 3 of 3
Your first real procedure
Time to touch the database. We'll build get_user, then create_user, then award_points — each one adds one new concept.
Setup
{"cmd": "create_index", "collection": "users", "field": "email"}
1. Read with validation
proc get_user(email) {
let user = find_one("users", {email: email})
if user == null {
abort "user not found"
}
return user
}
Concepts learned: let binding, find_one, null check, abort.
2. Create with idempotency
proc create_user(email, name) {
let existing = find_one("users", {email: email})
if existing != null {
abort "email already registered"
}
insert("users", {
email: email,
name: name,
points: 0,
created_at: "2026-04-26"
})
return {created: true, email: email}
}
Concepts learned: insert, the != operator, multi-field object literals.
3. Update with arithmetic
proc award_points(email, amount) {
if amount <= 0 {
abort "amount must be positive"
}
let user = find_one("users", {email: email})
if user == null {
abort "user not found"
}
update("users", {email: email}, {$inc: {points: amount}})
return {
email: email,
previous: user.points,
awarded: amount,
new_total: user.points + amount
}
}
Concepts learned: update with $inc, computing in return.
4. Combining the three
proc signup_and_award(email, name, signup_bonus) {
create_user({email: email, name: name})
award_points({email: email, amount: signup_bonus})
return {ok: true, signed_up: email, bonus: signup_bonus}
}
Concepts learned: procedures call procedures. Pass parameters as a single object literal.
5. Calling each from the wire
{"cmd": "call_procedure", "name": "create_user",
"params": {"email": "alice@example.com", "name": "Alice"}}
{"cmd": "call_procedure", "name": "award_points",
"params": {"email": "alice@example.com", "amount": 100}}
{"cmd": "call_procedure", "name": "signup_and_award",
"params": {"email": "bob@example.com", "name": "Bob", "signup_bonus": 50}}
6. Watching it fail
# Duplicate email
{"cmd": "call_procedure", "name": "create_user",
"params": {"email": "alice@example.com", "name": "Alice 2"}}
// → {"ok": false, "error": "email already registered"}
# Negative amount
{"cmd": "call_procedure", "name": "award_points",
"params": {"email": "alice@example.com", "amount": -5}}
// → {"ok": false, "error": "amount must be positive"}
Why this is powerful: all three operations of
signup_and_award happen in one transaction. If award_points fails, the create_user insert is rolled back. No client logic, no compensation queue, no retries.