Capstone: Build a Production-Grade API
Stage 3 · Backend & APIs · Capstone
This is the backend you put your name on — the one real users will trust with their data, their accounts, and their time.
🎯 The mission
You have spent 12 lessons learning every layer of a modern backend: how HTTP works, how to design clean REST routes, how to validate and handle errors gracefully, how to separate concerns across service and data-access layers, how to lock down endpoints with token auth, how to page through large datasets, how to fire background jobs, how to accept webhooks, how to version your API, and how to protect it under load.
Now you put it all together.
You are going to build one complete, production-grade backend API — from the first POST /auth/register to a deployed URL with secrets in environment variables. Pick an app you actually want to exist: a personal bookmarks API, a task manager, a notes app, a reading list, a movie watchlist. If nothing comes to mind, build Pocket Tasks — a task/project API where users can create projects, add tasks to them, and invite collaborators.
Whatever you pick, by the end of this capstone it will be something you could hand to a frontend developer and say "here's the docs — build anything you want on top of this."
🧱 What to build
Your API must satisfy every item in this checklist. These are not suggestions; they are the graduation requirements for Stage 3 D1.
Core resources
- At least one primary resource with full CRUD (Create, Read, Read-list, Update, Delete)
- At least one related/nested resource (e.g. tasks belong to projects, comments belong to posts)
- A sample endpoint table that you write yourself — see the example below
Example endpoint table for Pocket Tasks:
| Method | Path | Auth? | Description |
|---|---|---|---|
| POST | /auth/register | No | Create account, return JWT |
| POST | /auth/login | No | Return JWT |
| GET | /projects | Yes | List your projects (paginated) |
| POST | /projects | Yes | Create a project |
| GET | /projects/:id | Yes (owner) | Get one project |
| PATCH | /projects/:id | Yes (owner) | Update project |
| DELETE | /projects/:id | Yes (owner) | Delete project + tasks |
| GET | /projects/:id/tasks | Yes (owner) | List tasks (filter/sort) |
| POST | /projects/:id/tasks | Yes (owner) | Add a task |
| PATCH | /tasks/:id | Yes (owner) | Update a task |
| DELETE | /tasks/:id | Yes (owner) | Delete a task |
Design your own table first — then build to it.
Architecture layers
- A service layer that holds your business logic (no raw DB calls in route handlers)
- A data-access layer that wraps all database queries with parameterized statements (zero raw string interpolation into SQL or queries)
- Route handlers that only call services and return responses — nothing else
Validation and errors
- Server-side validation on every write endpoint (name required, length limits, type checks)
- Structured error responses in a consistent shape:
{ "error": "VALIDATION_FAILED", "message": "Title is required and must be under 200 characters.", "field": "title" } - Correct HTTP status codes throughout:
200for successful reads/updates201for successful creates400for bad input401for missing/invalid token403for ownership violations404for missing resources429for rate-limit hits500only when something truly unexpected breaks
Auth and ownership
- JWT-based token auth (or session tokens — your call)
- Every resource endpoint checks that the requesting user owns the resource before reading, updating, or deleting it
- Passwords hashed with bcrypt (never stored plain)
- No auth secrets in source code — use environment variables
List endpoint features
- Pagination (limit + offset, or cursor) on every collection endpoint
- At least one filter parameter (e.g.
?status=done,?project_id=123) - At least one sort parameter (e.g.
?sort=created_at&dir=desc) - Consistent response envelope for lists:
{ "data": [...], "meta": { "total": 48, "page": 1, "limit": 20, "has_more": true } }
Background jobs
- At least one background job: examples include sending a welcome email on registration, cleaning up deleted records after 30 days, recalculating a project's task completion percentage asynchronously, or generating a weekly digest
- The job must be genuinely asynchronous — the HTTP response must return before the job finishes
Rate limiting
- Rate limiting on at minimum the auth endpoints (
/auth/login,/auth/register) - A
429 Too Many Requestsresponse with aRetry-Afterheader when the limit is hit - Sensible defaults: 10 auth attempts per minute per IP is a reasonable starting point
Documentation
- A README that lists every endpoint with method, path, auth requirement, request body, and response shape
- Setup instructions: how to clone, install, set env vars, run locally, run tests
🗺️ Run it through B.U.I.L.D.
B — Before you write code, understand every layer
Map your domain on paper or in a doc. What are your entities? What relationships exist between them? What are the ownership rules? Who can see what? Write the answer to every one of these questions before you open your editor. If you can't answer them, you can't build correctly.
U — Understand your interface before you build it
Write your endpoint table first (see the example above). Define the request and response shapes for each route. Agree with yourself on the error envelope format. Lock in your pagination contract. Only once you have the interface documented should you start building the routes.
I — Implement bottom-up
Build in this order:
- Database schema and migrations
- Data-access layer (pure query functions — test them in isolation)
- Service layer (business logic on top of DAL — test this too)
- Route handlers (thin — call service, return response)
- Auth middleware
- Validation middleware
- Rate limiting
- Background jobs
- Docs
Skipping steps or mixing layers is how bugs become impossible to trace. Bottom-up means every layer you build on top is standing on tested ground.
L — Lock it down
Before you call this done, go through security explicitly:
- Can an unauthenticated request touch any protected endpoint?
- Can user A read, modify, or delete user B's resources?
- Is there any SQL/query injection surface (raw string interpolation)?
- Are secrets only in env vars, never in code?
- Are auth endpoints rate-limited?
- Does validation reject unexpected types, not just missing fields?
If you answered "yes, I checked" to all of these, proceed.
D — Document, test the critical path, then deploy
Write the README. Write at least a handful of tests that cover the most important path through your app — registration, login, create a resource, list it, delete it, verify it's gone. Deploy behind real environment variables, not a .env file committed to your repo.
🧪 Deliverables
When you submit this capstone, you need to have and be able to share all four of these:
1. A running API The server starts cleanly from a fresh clone using only the documented setup steps. Every endpoint in your table responds with the correct status codes and shapes.
2. A README documenting every endpoint Written in plain Markdown. No endpoint undocumented. Include at least one example request and response per route (curl or fetch is fine).
3. A test file covering the critical path
At minimum: register a user, log in, create a resource, retrieve it in a list, update it, delete it, confirm it's gone, and try to access someone else's resource and get a 403. These seven or eight assertions prove your API is fundamentally sound.
4. A deployed URL with secrets in environment variables Render, Railway, Fly.io, Heroku, Vercel (for serverless) — wherever you deploy, the URL must be live and the secrets must never appear in your codebase. Include the URL in your README.
🏆 Stretch goals
You have the foundation. If you want to push further:
- Webhooks with signature verification — let users register a callback URL; deliver events when resources are created or updated; sign the payload with HMAC-SHA256 and document the verification scheme
- Idempotency keys — accept an
Idempotency-Keyheader on write endpoints; replay the stored response for duplicate requests; protect users from double-charges or double-creates on retry - Cursor pagination — replace offset pagination with cursor-based pagination for large or frequently-updated collections; explain in your docs why cursor is more consistent
- OpenAPI spec — generate or hand-write an
openapi.yamlthat fully describes your API; host it with Swagger UI or Redocly; this is what senior engineers submit alongside a new service - API versioning — add a
/v2path for a breaking change you make to one endpoint; keep/v1working; document the migration path
Any one of these stretch goals would be impressive in a portfolio. All of them would be exceptional.
✅ You're done when…
- Every endpoint in your own endpoint table passes the API Design Checklist: correct method, correct path structure, correct status codes for success and each error case, consistent response envelope
- Your deployed API passes the Production-Readiness Checklist: starts from a fresh clone with only
npm install+ env vars, has no hardcoded secrets, serves real HTTPS, handles a malformed request without crashing, logs errors without leaking stack traces to the client - Your codebase passes the Security Audit Checklist: no raw string interpolation in queries, no plain-text passwords, all protected routes require a valid token, all resource routes check ownership, auth endpoints are rate-limited
- Every endpoint validates input, checks auth and ownership where required, returns the correct status code for both success and failure, and returns an error in your structured error shape when something goes wrong
A note on how you got here
You probably started this program by describing an app in plain English and watching it appear on screen. That was real. That was vibe coding doing what it does best — collapsing the distance between an idea and something you can click. Stage 3 does not replace that. It explains the engine underneath it. Every API you have ever used, every backend you have ever vibed into existence with an AI — it was built on exactly these twelve concepts. Now you know how to build the engine, not just ride it.
Ship something you are proud of.
➡️ Next: Stage 3 continues with Data & Databases — design the data layer your API deserves.
Build It Right, Or Don't Build It At All. 🏛️