"Reading about REST is not the same as building one. The capstone exercise: build a complete CRUD service that exercises every track from this quest. Once you've done it once, the patterns become reflex."
The Spec — One Resource, Eight Endpoints, Every Concept
Build a tiny /items service that exercises everything:
GET /items— list with cursor pagination + sorting + sparse fieldsets + filter.POST /items— create with server-assigned ID, return 201 + Location.GET /items/{id}— read with ETag + Cache-Control + 304 conditional GET.PUT /items/{id}— full replace with If-Match for optimistic locking (412 on conflict).PATCH /items/{id}— partial update.DELETE /items/{id}— remove, return 204.POST /items/{id}/publish— action endpoint (no natural noun for 'publish').POST /items/jobs— async bulk operation, return 202 + Location to a job-status resource.
Plus cross-cutting: CORS, request_id middleware, structured error envelopes, OpenAPI at /docs, Sunset header on a deprecated endpoint, Bearer token auth, rate limit.
The Build Order
- FastAPI app + Pydantic Item model + in-memory dict store.
GET /itemsreturns the list. - Add
POST /itemswith 201 + Location and server-generateditm_-prefixed IDs. - Add
GET /items/{id}. Then add ETag computation (sha256 of sorted JSON) and 304 on If-None-Match match. - Add
PUT /items/{id}with If-Match (412 if stale ETag),PATCHfor partial,DELETEwith 204. - Add cursor pagination (limit + cursor params, fetch limit+1 to detect has_more), filter (whitelist), sort (whitelist), fields (whitelist).
- Add action endpoint
POST /items/{id}/publishwith 202 if async. - Add async job pattern:
POST /items/jobsreturns 202 + Location,GET /items/jobs/{job_id}reports status. - Add CORS middleware, correlation-id middleware, custom error envelope.
- Add Bearer auth via FastAPI HTTPBearer dependency, with WWW-Authenticate on 401.
- Add slowapi rate limit (10/min per IP). Add Sunset header on a deprecated
/v1/itemsroute.
By the end, you've shipped a textbook-clean REST service in 200-300 lines of Python. Every track of this quest is exercised.
The Quality Bar
The endpoints work; now check the polish:
- Hit each endpoint with
curl -v; verify status codes match the spec. - Send the same POST twice with an Idempotency-Key; verify only one item is created.
- Issue a conditional GET with a stale ETag vs a current one; verify 200 vs 304.
- PUT with a stale If-Match; verify 412.
- Send a malformed body; verify the error envelope has code, message, request_id.
- Visit
/docs; verify Swagger UI shows every endpoint with its types. - Hit the rate limit; verify Retry-After header present.
- Hit the deprecated route; verify Sunset header.
Every "verify" is a moment of "yes, the protocol does what I think it does." That's the muscle memory you're building.
The capstone isn't a project; it's a reflex builder. Once you've wired up Location, ETag, If-Match, 412, correlation IDs, error envelopes, OpenAPI, and Sunset headers in one service, all of it becomes obvious. The next time you build a REST API, you're not reaching for tutorials — you're shaping the bytes deliberately.
Extensions for the Bold
Optional ambition beyond the basic spec:
- Add JWT instead of opaque tokens. Verify with PyJWT; pin algorithm; include claims (sub, exp).
- Add a SSE endpoint for streaming creates as they happen.
- Add a WebSocket endpoint for bidirectional collaborative editing.
- Add Redis as the dedupe store for Idempotency-Key instead of in-memory.
- Add OpenTelemetry traceparent propagation for distributed tracing.
- Generate a TypeScript SDK from the OpenAPI spec; use it from a tiny React frontend.
Each extension exercises a track in more depth. None are required for the capstone; all are well-trodden territory.
cwkPippa as the Cheat Sheet
If you get stuck building any part, cwkPippa's source is a worked example. Stuck on FastAPI middleware ordering? See
backend/main.py. Stuck on streaming responses? See backend/adapters/claude.py. Stuck on healing / idempotency? See backend/store/conversations.py. Reading working code beats reading tutorials when you have a specific question. cwkPippa is yours to crib from.