Back to course overview
Module 4Data & APIs 18 min

Lab: Persist everything

Wire the whole loop to the real database: schema + seed, four routes built and tested at the door, screens swapped off mock data, and the refresh test passed.

The biggest lab of the course — by the end, Replyable is a real app with a memory: messages live in SQLite, screens read through the API, replies and status changes persist across restarts. Take it in the numbered order; each step leaves the app working — which means the commits between steps are natural stopping points. Plan two or three evenings, and stop at commits, not mid-step.

  1. 1Set up the database: ask the assistant to add better-sqlite3, create lib/db.ts (connection + create-tables-if-missing from the schema lesson — adapt column names to your variant), and a lib/seed.ts that inserts your five mock messages into the database if it's empty. Run the app, open the DB viewer, see your rows. Commit: db + seed.
  2. 2Build the two GET routes and test them before touching any screen — visit /api/messages and /api/messages/1 directly in the browser and read the JSON. Testing doors before rooms is the habit that localizes every future bug to one side of the API line. Commit.
  3. 3Swap the Inbox and Thread off mock data: the screens now fetch from the routes (the assistant will use a data-fetching pattern — have it explain loading state in one paragraph, because you now have one: the moment between asking and answer, which needs a spinner or skeleton). The satisfying part: your components barely change — props are props, exactly as promised. Delete mock-data.ts with ceremony. Commit.
  4. 4Build the two write routes + wire the actions: PATCH for status (Send reply also flips status to replied; a Done button flips done), POST for replies (with door validation on all inputs — test the door directly with a bad payload and watch it refuse; the address bar only knocks with GET, so ask the assistant for a curl command, or paste a fetch('/api/replies', { method: 'POST', ... }) into the browser Console — the Console can knock on doors the address bar can't, and this becomes your standard write-route test). After wiring: send a reply in the UI, watch it appear, check the row in the DB viewer. Commit.
  5. 5The refresh test, then the restart test: reply to a message, refresh the browser — everything holds. Stop the dev server entirely, restart, everything still holds. That un-event is the whole module's deliverable. Add one 'paste in a new message' form (your v1 stand-in for email integration — it's on the cut list, remember) so the app can actually receive new work. Final commit: v1 skeleton walks.
Lab 4 problem set

Door-testing reps: hit each route with three malformed requests and verify the refusals (wrong status value, empty body, nonexistent id); trace one full click-to-database round trip in writing ('click → fetch → route → SQL → JSON → state → redraw'); and diagnose two planted bugs from symptoms alone — 'saves but doesn't show' vs. 'shows but doesn't save' — naming which side of the API line each lives on before you look. You now think in three places; the problem set proves it.