Tour: JSON API
Rex handlers that return objects or arrays get automatically serialized to JSON
with content-type: application/json. Combined with the KV store, this gives
you a complete CRUD API in a few lines of Rex.
Endpoints
GET /api/articles — list all articles
POST /api/articles — create article
GET /api/articles/:slug — get one article
PUT /api/articles/:slug — update article
DELETE /api/articles/:slug — delete article
All endpoints require an Authorization header (enforced by
api/_middleware.rex).
Try It
Step 1: Seed an API key
sqlite3 examples/knowledge-base/data.db "INSERT INTO kv VALUES('keys:demo','1')"
Step 2: Create an article
curl -X POST http://localhost:4000/api/articles \
-H 'Authorization: demo' \
-d '{"slug":"hello","title":"Hello World","body":"# Hello
Created via API."}'
Step 3: List articles
curl http://localhost:4000/api/articles -H 'Authorization: demo'
Step 4: Try without auth (expect 401)
curl http://localhost:4000/api/articles
How It Works
Rex's existence-based semantics make request handling natural. when checks if
a value is defined (not just truthy), so when method == "GET" branches cleanly.
Return an object literal and the server serializes it:
when method == "GET" do articles = db.list("article:") items = [json.parse(a.value) for a in articles] {ok: true, articles: items} end
Database
The db.* opcodes provide a simple key-value store backed by SQLite.
The database file is created automatically on first run.
db.get(key)— returns the value ornonedb.set(key, value)— upserts a string valuedb.del(key)— removes a keydb.list(prefix)— returns all entries matching a prefix
articles.rex Source
/* Article collection: GET list, POST create */ when method == "GET" do articles = db.list("article:") items = [ json.parse(a.value) for a in articles ] return { ok: true articles: [ { slug: a.slug title: a.title updated: a.updated } for a in items ] } end when method == "POST" do input = json.parse(body) unless input.slug and input.title and input.body do res.status = 422 return { ok: false error: "slug_title_body_required" } end record = { slug: input.slug title: input.title body: input.body created: time.now() updated: time.now() } db.set(`article:${input.slug}`, json.stringify(record)) res.status = 201 return { ok: true slug: input.slug } end res.status = 405 { ok: false error: "method_not_allowed" }
articles/[slug].rex Source
/* Single article: GET, PUT, DELETE */ slug = params.slug unless slug do res.status = 400 return { ok: false error: "missing_slug" } end when method == "GET" do record = db.get("article:" + slug) unless record do res.status = 404 return { ok: false error: "not_found" } end return { ok: true article: json.parse(record) } end when method == "PUT" do input = json.parse(body) existing = db.get("article:" + slug) unless existing do res.status = 404 return { ok: false error: "not_found" } end old = json.parse(existing) updated = { slug: slug title: input.title or old.title body: input.body or old.body created: old.created updated: time.now() } db.set("article:" + slug, json.stringify(updated)) return { ok: true slug: slug } end when method == "DELETE" do db.del("article:" + slug) return { ok: true deleted: slug } end res.status = 405 { ok: false error: "method_not_allowed" }