Week 6 Checkpoint: API + DB Mini-Service
- 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
| Spaced-rep: full Week 6 deck, fumbles logged | 12 min |
| Closed-book recall drills (schema, joins, async, HTTP) | 18 min |
| Build milestone 1: service + storage, first commit | 35 min |
| Build milestone 2: tests + index ritual, second commit | 35 min |
| README, error log update, cumulative quiz | 15 min |
Builds on: Day 36 β SQL I β tables & queries Β· Day 38 β Database internals β indexes & transactions Β· Day 40 β Concurrency & async Β· Day 41 β HTTP & FastAPI
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.
"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
Closed-book recall: wire the week together
18 minEditor closed for each item; open only to check. Log misses in your error log with causes.
- 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?
- 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.
- 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).
- Run the week's flashcard deck. Under 80% β schedule the weak day's re-read before Day 43.
Build milestone 1 β service + storage
35 minBuild notes_service/ in your practice repo: app.py, db.py, test_app.py. Milestones, each verified before moving on:
db.py:init_db(path)creates the notes table (constraints from your recall drill β now checked);get_conndependency yields a per-request connection; CRUD functions (create_note,list_noteswith optional tag filter + limit/offset,get_note,update_note,delete_note) β all SQL lives here, all writes insidewith conn:.app.py: pydanticNoteIn(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.- Run with uvicorn; create, search, update, and delete notes through /docs. Watch the request log tell the story.
- Commit at this milestone before testing begins β working service, then safety net, two commits.
On your own
Build milestone 2 β the safety net and the index ritual
35 mintest_app.pywith 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.- Run pytest until green. Any bug you fix: add the regression test first (Day 19's habit).
- 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.
- 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 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.
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.
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
- Preview: dependency injection as architecture β β Your get_conn dependency is a first taste of DI β Day 45 generalizes it into the routes/services/repos layering. Skim FastAPI's Dependencies chapter with that lens.
- 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
β 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