Day 42 Β· The first backend

Week 6 Checkpoint: API + DB Mini-Service

You will be able to
  • Retrieve Week 6's systems concepts (SQL, indexes, processes, async, HTTP) from memory
  • Build a FastAPI notes service backed by SQLite with full CRUD and validation
  • Test every route and failure path with FastAPI's TestClient
  • EXPLAIN the service's hottest query and add the index it needs
  • Ship the week's work as one committed, tested, documented artifact
Today's ~115 minutes
Spaced-rep: full Week 6 deck, fumbles logged12 min
Closed-book recall drills (schema, joins, async, HTTP)18 min
Build milestone 1: service + storage, first commit35 min
Build milestone 2: tests + index ritual, second commit35 min
README, error log update, cumulative quiz15 min

Builds on: Day 36 β€” SQL I β€” tables & queries Β· Day 38 β€” Database internals β€” indexes & transactions Β· Day 40 β€” Concurrency & async Β· Day 41 β€” HTTP & FastAPI

The analogy

All week you toured a restaurant's stations: the pantry with its labeled, contracted shelves (SQL), the index cards that find any ingredient in seconds (B-trees), the hotel-manager kitchen keeping cooks out of each other's pots (OS), the one clever chef juggling simmering pans (async), and the counter where orders are taken in a strict verb-and-status protocol (HTTP). Today you open the restaurant. Small menu β€” a notes service: write a note, find your notes, edit, delete β€” but every station wired together and actually serving.

This "boring CRUD app" is secretly the most-built object in software, and building it end-to-end forces the connections the week only implied: the request's JSON becomes a parameter-bound INSERT; the search endpoint becomes a WHERE that needs one of Day 38's indexes; the status code you return IS the contract with your caller. And because it is a review day, you build some of it with the book closed first β€” recalling the LEFT-join idiom or the 422-vs-404 decision from memory is today's spaced-repetition workout, hidden inside shipping something real.

Why this matters on the job

"Build a small CRUD API against a database, with tests" is a literal take-home interview task at many companies β€” today is a dress rehearsal with a rubric. It is also the load-bearing skeleton of your future: Day 45 hardens exactly this service with auth and layers, Day 49's URL shortener re-runs the pattern under design constraints, and the capstone's serving layer (Day 119 onward) is this shape with retrieval inside. The TestClient habit you start today β€” testing the API contract, not the implementation β€” is what makes every later refactor (Day 45's, Day 148's containerization) safe.

Guided practice

guided 1

Closed-book recall: wire the week together

18 min

Editor closed for each item; open only to check. Log misses in your error log with causes.

  1. On paper, write the CREATE TABLE for notes (id, title, body, tag, created_at) with two constraints from memory, and the parameter-bound INSERT for it. Check against Day 36's rules β€” did you remember the ? placeholders and a CHECK?
  2. On paper: the anti-join that finds tags never used by any note (two-table version), and where an index belongs if searches filter by tag. Check against Days 37–38.
  3. Out loud, 60 seconds each: why a blocking call in an async handler freezes ALL requests (Day 40); what SIGTERM should make your uvicorn process do (Day 39); why DELETE returning 204 twice in a row is fine but POST retries are not (Day 41).
  4. Run the week's flashcard deck. Under 80% β†’ schedule the weak day's re-read before Day 43.
guided 2

Build milestone 1 β€” service + storage

35 min

Build notes_service/ in your practice repo: app.py, db.py, test_app.py. Milestones, each verified before moving on:

  1. db.py: init_db(path) creates the notes table (constraints from your recall drill β€” now checked); get_conn dependency yields a per-request connection; CRUD functions (create_note, list_notes with optional tag filter + limit/offset, get_note, update_note, delete_note) β€” all SQL lives here, all writes inside with conn:.
  2. app.py: pydantic NoteIn (title 1–120 chars, body non-empty, optional tag) and routes: POST /notes β†’ 201, GET /notes?tag=&limit=&offset=, GET /notes/{id} β†’ 200/404, PUT /notes/{id} β†’ 200/404, DELETE /notes/{id} β†’ 204/404. Handlers call db.py functions only β€” no SQL in routes.
  3. Run with uvicorn; create, search, update, and delete notes through /docs. Watch the request log tell the story.
  4. Commit at this milestone before testing begins β€” working service, then safety net, two commits.
🐍 python β€” editable, runs in your browser
Ctrl/⌘+Enter runs · Tab indents · numpy/pandas/sklearn auto-load on import (torch and network calls need a local run)

On your own

Build milestone 2 β€” the safety net and the index ritual

35 min
  1. test_app.py with a fixture giving each test a TestClient wired to a fresh tmp_path database (override the get_conn dependency — look up app.dependency_overrides). Write the contract tests: create→201 with echoed fields; get missing→404; invalid body (empty title, 200-char title)→422; tag filter returns only matching; pagination slices correctly; update changes what it should; delete→204 then get→404; delete again→404. Aim for every route's happy AND failure path — about 10 tests.
  2. Run pytest until green. Any bug you fix: add the regression test first (Day 19's habit).
  3. The index ritual: seed 5,000 notes across 20 tags (script or fixture), EXPLAIN QUERY PLAN the tag search (SCAN), CREATE INDEX on tag, re-EXPLAIN (SEARCH), and record both plans plus timings in the README.
  4. Commit. Two commits minimum today: service, then tests+index.

No hints beyond the milestones β€” the week taught every piece. Struggle first; your toolkit files second.

Ship before you stop

Ship it: the Week 6 artifact

Finish the notes service as a portfolio-grade mini-repo: working CRUD API (SQL confined to db.py), ~10 passing TestClient tests covering all routes' happy and failure paths, the tag index justified by recorded EXPLAIN plans, and a README containing: how to run (venv, uvicorn, pytest), the API table (route, method, statuses), your index before/after evidence, and a five-line "what I'd harden next" list (auth? rate limits? β€” foreshadowing Days 44–45). Update your error log with today's recall misses. This repo is the direct ancestor of Day 45's hardened service and Day 49's URL shortener.

Rubric β€” check what you completed (0/6)

Common mistakes & misconceptions

  • Testing by clicking /docs instead of writing TestClient tests. Manual checks vanish; the suite re-verifies the whole contract in seconds forever β€” and take-home graders read tests first.
  • Sharing one module-level sqlite3 connection across threaded requests β€” sqlite3 objects default to single-thread use and will raise (or corrupt ordering). Per-request connections via a dependency.
  • Letting tests share a database. Test A's leftover notes break test B only when order shifts β€” the classic flaky suite. Fresh tmp_path DB per test, via fixture.
  • Skipping the 404/422/204 failure-path tests because "the happy path works." The failure contract IS half your API; it is also where regressions hide.
  • Writing UPDATE without checking rowcount β€” updating a missing id "succeeds" silently and returns 200 for a note that does not exist. rowcount 0 β†’ 404.
  • Indexing before EXPLAINing (or after guessing). The ritual is: read the plan, see SCAN, add the index the plan needs, confirm SEARCH. Evidence, then action.
Knowledge check

Q1. Your tag-search endpoint is slow with 50k notes. EXPLAIN QUERY PLAN says "SCAN notes". The fix and the proof?

Q2. PUT /notes/9999 (nonexistent) runs an UPDATE that matches zero rows. Correct API behavior?

Q3. Why must each test get a fresh database via fixture instead of sharing notes.db?

Go deeper β€” curated resources

docsFastAPI β€” Testing (official tutorial) β†—20 mindocssqlite3 β€” official Python docs (transactions & placeholders refresher) β†—10 mindocspytest β€” fixtures documentation β†—15 min
If you have a third hour
Done means
  • Recall drills done closed-book; misses logged with causes
  • Service runs; all routes behave per the API table
  • pytest suite β‰₯ 10 tests, green, isolated by fixture
  • Index evidence (plans + timings) in the README; two+ commits pushed
  • Cumulative quiz β‰₯ 2/3
How this connects

← Back: Today compiled the whole week into one artifact: Day 36–37's SQL behind Day 41's routes, Day 38's EXPLAIN ritual on Day 40's threadpool-served handlers, tested with Day 18–19's pytest discipline, shipped with Day 20's git habits.

Forward β†’: Day 45 hardens exactly this service (auth, layers, middleware); Day 49's URL shortener re-runs the build under design constraints; Day 148 containerizes it; and the capstone's API layer (Day 119+) is this skeleton with retrieval and citations inside.

Unlocks: D45 Web Service Architecture