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.
Report Issue