Chapter 45 / 51
Volume 5 · Extensions, Ops & Projects

Project 1: a REST API service (a todo backend)

One chapter to string the first four volumes together: a REST service with persistence, auth, pagination and hot reload — from empty directory to acceptance.

Overview

Positioning: the first of three projects, and the most universally useful. Recommended: actually build it — each section ends with an acceptance point; passing them all completes the project. Reference: demo-single's app.h/item.h is the mature form of exactly this project.

45.1 Project goal

Build a "todo" backend (any resource works the same):

CapabilityEndpointsVolumes used
Resource CRUDGET/POST /api/v1/todos, GET/PUT/PATCH/DELETE /api/v1/todos/{id}Volume 3 (14–18)
PersistenceSQLite, zero data loss across swapsch. 32 + ch. 28
Pagination & filtering?page=&size=&done=ch. 18
Token authX-Api-Token headerch. 27/39
Ops surface/health, /stats, /admin/reloadch. 26–27/38
Deployment shapesingle directory + systemdch. 42

45.2 The foundation (30 minutes)

  • Skeleton: lay out directories per ch. 05's conventions (modules/ route_http/ db/ wwwroot/); main.c orchestrates includes; xs.json gets the minimal config per ch. 07/08
  • Three modules: modules/http.h (ch. 15's Reply family), modules/db.h (ch. 32's DB_Init/Unit), modules/route.h (ch. 17's two tables)
  • Table: todo(id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, done INTEGER DEFAULT 0, created INTEGER)
Acceptance 1: the service starts, all five log segments present (ch. 02's dictionary), /health returns ok.

45.3 CRUD & conventions

Create and update (the two core pieces)
/* POST /api/v1/todos: read JSON (ch. 16+18) → bound insert (ch. 32) */
static void Handle_Create(XS_HttpReq* pReq, const RouteParamHTTP* a, uint32 n)
{
    xvalue* pBody = ReadRequestJSON(pReq);        /* wrapper: body→parse */
    if ( pBody == NULL ) return (void)ApiError(pReq, 400, "bad_json");
    xvalue* pTitle = xrtValueObjectGet(pBody, "title");
    if ( pTitle == NULL || !IsNonEmptyString(pTitle) )
        return (void)(xrtValueRelease(pBody),
            ApiError(pReq, 422, "field_required:title"));
    ...
    int64 iId = DB_InsertTodo(ViewOf(pTitle));
    xrtValueRelease(pBody);
    ReplyItemJSON(pReq, 201, iId);            /* 201 + the new resource */
}

/* PUT/PATCH /api/v1/todos/{id}: path params + partial update */
/* the three-way error contract: 404 (absent) / 422 (field) / 200 (ok) */

Error contract first (the key habit in API work): 400 malformed JSON, 401 token, 404 resource, 405 method (routing automatic), 413 oversized (driver automatic), 422 semantic — each with a machine-readable code field. PUT on an absent resource: 404 or upsert? Pick one and document it; never both.

Acceptance 2: all seven CRUD endpoints pass curl; the three-way error contract behaves correctly under three deliberate inputs.

45.4 Pagination & auth

  • Pagination: the list endpoint SELECT ... LIMIT ? OFFSET ? + a total SELECT COUNT(*); response {"total":N,"page":P,"data":[...]} — an upper clamp (e.g. size ≤ 100) is a mandatory defense
  • Auth middleware: one module-layer CheckToken(pReq) (constant-time comparison), called as the first line of every /api/* handler; the token lives in a Custom knob (ch. 09's strict reading)
  • Hot-reload drill: change the list ordering rule → reload → verify (a) zero in-flight errors (b) data intact (ch. 32 generations) (c) counters continuous across swaps (Swap carries a total counter — ch. 28's graduation check)
Acceptance 3: three swaps under load with zero failures; the total counter continuous; wrong tokens 401 with rate limiting active.

45.5 The acceptance checklist

DimensionStandardChapter basis
FunctionCRUD/pagination/filtering/auth all passthis chapter
ProtocolHEAD supported (GET|HEAD registration), 405+Allow correctch. 15/17
Persistencezero loss across restarts and swapsch. 32
Resiliencezero failures swapping under load; all four limit states correctch. 28/09
Observability/health /stats usable; request logs carry IDsch. 43
Deploymentsystemd unit + a graceful-shutdown windowch. 42

Common mistakes

Writing endpoints before fixing the error contract
Symptom
Unifying the error format late means touching every handler.
Cause
The error contract is the API's constitution — fix it in 45.3 step one (the ApiError helper + a code table).
No clamp on the size parameter
Symptom
One ?size=1000000 and the service starts deep-pagination scans.
Cause
Pagination parameters are client input — an upper clamp and a default are part of the interface.

Exercises

BasicComplete the project

Pass every acceptance point from 45.2–45.5.

Acceptance: all six checklist dimensions green.
AdvancedConcurrent-edit semantics

Add an updated field with If-Match optimistic locking (409 on conflict).

Acceptance: of two concurrent PUTs exactly one succeeds.
ChallengeAn OpenAPI documentation route

Implement /api/openapi.json: describe the endpoints with xvalue (route-table-driven docs).

Acceptance: zero drift between docs and implementation (same-source generation).

Summary

PointConclusion
Flowskeleton → three modules → error contract → CRUD → pagination/auth → acceptance
Key habitserror contract first; parameter clamps; a unified auth entry
Graduation markzero failures swapping under load + data intact + counters continuous
API references in this chapter
xrt further reading