Wedge: reactive full-stack web — see
STRATEGY.md.
Track: Backend (this document) — unlocks full-stack reactivity (likely the true signature).
Prior track: Frontend (FRONTEND_ROADMAP.md)
Master checklist:TaskList.md
Live Graph (DB → wire → pixel) is the reactive spine; this track is the HTTP/app framework that lets you build real servers without hand-rolling every IF path EQUALS.
Thesis
Luke apps should declare routes, binds, and sessions the same way they declare reactive cells — declarative surface, native Build cost, no glue frameworks.
Gap status
| Gap | Status | Beachhead |
|---|---|---|
Parameterized SQL (? binds) |
✅ | dbExecBind / dbQueryBind + LIST of TEXT; TLS conn pool + stmt cache + WAL |
| Networked DB (Postgres / libpq) | ✅ | std/pg — Slipstream default — NETWORK_DB_ROADMAP.md, numbers in BACKEND_BENCHMARKS.md |
| Exam ops dashboard | ✅ | Exam/ — Luke watching itself (request log → IVM → live Material UI) |
| Path params + match | ✅ | httpMatch path, "/user/:id", params |
| Method-aware dispatch | ✅ | SERVE ROUTES ON server WITH n codegen from ROUTES + HANDLE |
| Query string → MAP | ✅ | httpQueryMap |
| Headers / cookies | ✅ | httpHeader / httpCookie / httpSetCookie |
| JSON body | 🟡 | httpBody + jsonParse (stdlib sugar later) |
Form body (application/x-www-form-urlencoded) |
✅ | httpFormMap + FORM / VALIDATE FORM → form_*_error / form_*_ok cells |
| Auth / session / login | 🟡 | std/auth — Argon2id, sessions, CSRF, scoped WATCH, SECRET, FLOW, LIMIT/remaining, REVEAL, WHO SAW SINCE, SCRUB TO access |
| Middleware / filters | 🟡 | REQUIRE LOGIN / REQUIRE CSRF; MIDDLEWARE ORDER AUTH THEN RATE LIMIT (order compile check) |
| Declarative route table syntax | ✅ | ROUTES / HANDLE / LINK TO / SERVE ROUTES — broken link / missing HANDLE / SECRET without auth = compile error |
| SSE channel auth / backpressure | 🟡 | PUSH WATCH of SECRET requires FOR CURRENT USER; scheduler lanes = backpressure story |
| Password reset / 2FA / OAuth | 🟡 | FLOW + VERIFY BY CODE\|TOTP\|OAUTH wrapper only — no invented providers (interop/conformance) |
Declarative LIMIT + reactive remaining |
🟡 | LIMIT login TO N PER … + login.remaining + REFRESH LIMIT |
| Migrations / schema helpers | ✅ | SCHEMA / ENSURE SCHEMA; MIGRATION / MIGRATE / REWIND + luke_schema_migrations |
| C10K concurrency (pool / evented I/O) | ✅ | SO_REUSEPORT multi-loop + per-loop pools; keep-alive 100k; idle timeouts; graceful SIGTERM — see DEPLOY.md |
| HTTP/1.1 keep-alive / chunked / streaming | ✅ | keep-alive reuse; httpChunkOpen/httpChunk/httpChunkEnd; SSE unchanged |
| TLS | ✅ | Reverse proxy (Caddy/nginx) — no invented in-process TLS; LUKE_TRUST_PROXY + httpClientIp |
Auth rules (non-negotiable)
- No homegrown crypto — passwords via libsodium Argon2id (
crypto_pwhash_str/_verify); randomness viarandombytes_buf; compares viasodium_memcmp; audit chain viacrypto_generichash. - Password = hash, not encryption — plaintext never stored; “auto encrypt everything” is out of scope (key management is the hard part).
- Secure path together — hash + timing-safe verify + session cookie (HttpOnly, SameSite=Lax;
LUKE_AUTH_SECURE=1adds Secure) + CSRF. Not hash-only. - Live Graph + auth —
WATCH … FOR CURRENT USERbindsuser_id = ?per request (no shared IVM across tenants). - Auth-as-types —
SECRETon an unscoped path is a compile error;FLOWDONEwithoutVERIFYis a compile error; declassify viaREVEAL. SeeAUTH.md. - Whole-stack compile gates (beachheads) — broken
LINK TO, SECRET route without auth, middleware order inversion, OAuth withoutVERIFY BY OAUTH, unknown SCHEMA types → compile error.
Examples: auth_unit.luke, auth_api.luke, auth_scoped.luke, auth_secret_ok.luke, auth_flow_ok.luke, auth_lang_ok.luke, backend_lang_ok.luke, backend_routes_serve.luke, backend_form_errors.luke, backend_migrate_ok.luke (+ negatives backend_routes_bad_*.luke, backend_mw_bad_order.luke, backend_flow_oauth_bad.luke).
War cry surface (this beachhead)
import std/serverimport std/sqlitefn health(req: Request) { httpReply(req, 200, "text/plain", "ok")}fn show_user(req: Request) { let path = httpPath(req) var params: map = {} httpMatch(path, "/user/:id", params) let id = params["id"] httpReply(req, 200, "text/plain", id)}raw "ROUTES DO"raw "GET \"/ok\" HANDLE health"raw "GET \"/user/:id\" AS INTEGER HANDLE show_user"raw "END ROUTES"let server = httpListen(8799)raw "SERVE ROUTES ON server WITH 8"
Form validation feeds reactive cells (BIND in UI without a second declaration):
raw "FORM login DO"raw "HAS email AS EMAIL"raw "HAS password AS PASSWORD"raw "END FORM"raw "VALIDATE FORM login FROM params"raw "# → form_login_email_error / form_login_ok cells"
Migrations are versioned UP/DOWN SQL (rewind = apply DOWN):
MIGRATION app DO VERSION 1 UP "CREATE TABLE items(id INTEGER PRIMARY KEY)" DOWN "DROP TABLE items"END MIGRATIONMIGRATE app ON db TO 1REWIND app ON db TO 0
Examples: examples/build/sql_bind.luke, examples/build/backend_api.luke, examples/build/backend_routes_serve.luke.
Sequencing
- Secure data path — parameterized binds (done); migrate demos off string-concat SQL.
- Request shape — match + query map + headers/cookies + form cells (done); richer JSON helpers next.
- Session / auth — opaque sid cookie + DB table (beachhead); do not invent OAuth/TOTP providers — wrap textbook flows only.
- Declarative routes —
ROUTES+HANDLE+SERVE ROUTEScodegen (done); no clever auto-dispatch invention. - Schema migrate/rewind —
MIGRATION/MIGRATE/REWIND(done). - Hardening — middleware depth, request limits, SSE auth, structured errors.
Related
LIVE_GRAPH.md— reactive DB→pixelBUILD_MODE.md—std/server/std/sqliteinventorySTRATEGY.md— why Backend is the next trackTaskList.md— cross-track checklistAUTH.md— auth-as-types / FLOW / SECRET