Project 9

A JSON API in Node

Apologetic question: "What does it mean to serve?"

Project 9 — A JSON API in Node

“Whoever would be great among you must be your servant, and whoever would be first among you must be slave of all.” — Mark 10:43–44

Chapter: 9 — Your First Server: HTTP, Ports, and Node.js Due: End of Week 9 Submit: A public GitHub repo URL containing server.js, supporting source files, README.txt, and agent-log.txt. Coding 3 uses a real local toolchain and git — see Appendix A for the Node install, the editor, and the git workflow. Allowed tools: Node.js (built-in http module only — no Express, no framework), curl, a real editor, git, the textbook, and agentic AI. AI policy — Phase 2 (wk 9–16): agentic AI is ON, and an agent-log.txt is REQUIRED. Log every task you delegated to an agent, what it did, where it went wrong, and where you intervened. This project is deliberately shaped so the agent cannot finish it alone: the endpoint design and the status-code decisions are yours. The grader reads your log and your reasoning, not just your code.


The Setup

A small church runs a prayer ministry. Right now the requests live on index cards in a shoebox by the welcome desk, and the only person who knows what’s in the box is the one volunteer who carries it home on Sundays. The pastor wants a tiny web service so that — eventually — a phone app and the church website can both read and add prayer requests from anywhere.

You are not building the app or the website this week. You are building the server they will both talk to: a JSON API that holds a list of prayer requests in memory and answers HTTP requests about them. It is the first brick of a real system, and the church will judge it by one standard — does it stay up, and does it tell the truth about what happened?

You will build it with Node’s built-in http module and nothing else. No Express. No framework. Next week you rebuild this exact API in FastAPI and feel the difference; the point of doing it bare this week is that you will know precisely what the framework hides.

The data is simple — a prayer request is { id, title, answered } — so that the server is the lesson, not the domain. The discipline of correct methods, correct status codes, validated input, and a process that does not crash on a bad request is identical whether you’re serving prayer requests or banking transactions. Build the small one right and the big one is the same shape.


Learning Targets

By completing this project, you will demonstrate that you can:

  • Stand up an HTTP server from zero with Node’s http module — no framework.
  • Route requests by method + path to the right handler.
  • Read and parse a JSON request body that streams in as chunks.
  • Choose the correct status code for each outcome (200, 201, 400, 404) and defend each choice.
  • Validate client input at the door and reject bad input with a clear 400.
  • Keep the server alive through malformed input instead of crashing.
  • Test every endpoint with curl and document the API so another engineer could use it.
  • Direct an agent to build route handlers while keeping the endpoint design and status-code judgment in your own hands — and log exactly where that line fell.

Normal Tier

Goal: A working in-memory JSON API for prayer requests, with GET (list + by id) and POST, correct status codes throughout, tested with curl and fully documented.

Required features

  1. GET /requests — returns the full list as a JSON array, status 200.
  2. GET /requests/:id — returns the one request with that id (status 200), or status 404 with a clear error message if no request has that id.
  3. POST /requests — reads a JSON body { "title": "..." }, creates a new request with a fresh unique id and answered: false, and returns status 201 with the created object.
  4. Correct status codes everywhere. 200 for a successful read, 201 for a successful create, 404 for a missing resource, 400 for a bad request body. Returning 200 for everything is an automatic loss of the status-code points — the status code must tell the truth.
  5. Input validation. A POST with a missing or empty or non-string title returns 400 with a message saying what was wrong. A POST with malformed JSON returns 400 and the server stays up.
  6. In-memory data. A plain array, seeded with 2–3 requests. It is fine that the data dies on restart — persistence is Week 11’s job. Say so in your README.
  7. A catch-all 404 for any unmatched method+path.
  8. Every endpoint documented in the README: method, path, what it does, an example curl command, and the status codes it can return.

Example session

$ curl -i http://localhost:3000/requests
HTTP/1.1 200 OK
Content-Type: application/json

[{"id":1,"title":"Healing for Pastor John","answered":false}, ...]

$ curl -i http://localhost:3000/requests/99
HTTP/1.1 404 Not Found
Content-Type: application/json

{"error":"no request with id 99"}

$ curl -i -X POST http://localhost:3000/requests \
    -H "Content-Type: application/json" -d '{"title":"Safe travels"}'
HTTP/1.1 201 Created
Content-Type: application/json

{"id":4,"title":"Safe travels","answered":false}

$ curl -i -X POST http://localhost:3000/requests \
    -H "Content-Type: application/json" -d '{not json}'
HTTP/1.1 400 Bad Request
Content-Type: application/json

{"error":"body must be valid JSON"}

Normal-tier rubric (out of 100)

CriterionPoints
Server starts on a port and serves (no framework — http only)8
GET /requests returns the list with 20010
GET /requests/:id returns the item with 200, or 404 when missing14
POST /requests creates and returns the item with 20114
Status codes are correct in every case (graded against the table you document)14
Input validation rejects missing/empty/non-string title with 4008
Malformed JSON returns 400 and the server stays up8
Catch-all 404 for unmatched routes4
Every endpoint documented in README with method, path, status codes, and a curl example8
agent-log.txt present, honest, and showing the human owned the endpoint/status design8
Code decomposed (a send helper, route handling readable; not one tangled blob)4

Medium Tier (+up to 25% extra credit)

M1. Full CRUD — add PUT and DELETE

  • PUT /requests/:id — replace the mutable fields (title, answered) of the request with that id from the JSON body. Return 200 with the updated object, or 404 if it doesn’t exist. Validate the body the same way POST does.
  • DELETE /requests/:id — remove the request with that id. Return 200 (or 204 No Content) on success, 404 if it didn’t exist. DELETE carries no body.

Document both new endpoints in the README with curl examples.

M2. Body validation with clear 400s, and malformed-JSON survival

Make validation thorough and the messages genuinely useful. Every reject must say what was wrong, specifically — “title is required and must be a non-empty string”, not “bad request”. Every JSON.parse of client input is wrapped so the server never crashes on garbage, oversized, or empty bodies. Demonstrate it: include in your README a curl command that sends broken JSON, followed by a command proving the server is still answering afterward.

M3. A /health endpoint

Add GET /health returning 200 with {"status":"ok","count": <number of requests>}. Explain in one README line why a server exposes a health endpoint (monitoring/operators ask “are you alive?” without touching real data). Confirm it returns 200 even when the request list is empty.


Hard Tier (+up to 25% additional extra credit)

The Hard tier makes you feel the event loop from Week 8 and then forces the judgment an agent cannot make for you.

H1. Prove the event loop keeps serving (the slow endpoint)

Add GET /slow that takes about 5 seconds to respond, implemented the right way — with setTimeout(callback, 5000) (non-blocking), not a busy-wait loop. Then demonstrate, with timestamps, that while one client is waiting on /slow, other requests to /health or /requests are answered immediately — the single thread is not frozen, because setTimeout does not block it.

Then add GET /slow-blocking that takes ~5 seconds the wrong way — a synchronous busy loop (while (Date.now() - start < 5000) {}). Demonstrate that while this one runs, every other request is frozen until it finishes, because the one thread is stuck computing and the event loop cannot turn.

Capture both demonstrations (two terminals, timestamped curl output, or a small script) and put the transcripts in your README or a demo/ folder. The contrast is the deliverable: non-blocking I/O lets the loop serve; a blocking computation freezes it.

H2. The decision memo — MEMO.docx

Write a one-page memo titled “When is Node’s single-threaded event loop the right tool, and when is it the wrong one?” It must:

  • State, in your own words, why H1 came out the way it did — connect setTimeout (non-blocking I/O, the loop keeps turning) and the busy loop (blocking computation, the loop is frozen) directly to the single-thread/event-loop model from §8.7 and §9.4.
  • Name two realistic workloads Node’s event loop is the right tool for, and say why (many simultaneous connections, mostly waiting on I/O — a chat backend, an API gateway, this prayer service at scale).
  • Name two realistic workloads it is the wrong tool for, and say what you’d reach for instead and why (CPU-bound work — image processing, a large simulation — where one long computation freezes every connection; reach for processes/workers, tying back to Week 8’s multiprocessing).
  • Cite your own H1 numbers as evidence.

This memo is the piece an agent cannot write for you, because it requires judgment about your measurements and your constraints. It is graded most heavily of the Hard tier.


Submission

Submit one URL via the course portal: a public GitHub repo, one repo for this project. See Appendix A for the git workflow.

What the repo must contain

  1. server.js — your API. Plus any supporting files you factored out (store.js for the in-memory data, respond.js for the send helper, etc.). Good decomposition is rewarded; one giant file is not.
  2. README.txt — see the template below.
  3. agent-log.txt — the required Phase 2 log (template below). Every task you delegated, what the agent produced, where it was wrong, where you intervened, and — explicitly — which decisions you reserved for yourself.
  4. (Hard tier) MEMO.docx and the /slow demonstration transcripts.
  5. The server left runnablenode server.js starts it; the README says which port and how to test it.

README.txt template

# Project 9 — A JSON API in Node

**Tier targeted:** Normal / Medium / Hard
**Run it:** `node server.js`  (listens on http://localhost:3000)
**Test it:** see the endpoint table below

## Endpoints

| Method | Path            | Description           | Status codes        | Example curl |
|--------|-----------------|-----------------------|---------------------|--------------|
| GET    | /requests       | List all requests     | 200                 | `curl ...`   |
| GET    | /requests/:id   | One request by id     | 200, 404            | `curl ...`   |
| POST   | /requests       | Create a request      | 201, 400            | `curl ...`   |
| ...    | ...             | ...                   | ...                 | ...          |

## Data
In-memory array, seeded with N requests. Resets on restart (persistence is Week 11).

## Design decisions (mine, not the agent's)
- Why /requests/:id returns 404 (not 200 + null) when an id is missing: ...
- Why POST returns 201 (not 200): ...
- (Hard) Why /slow uses setTimeout and what /slow-blocking proves: ...

## AI usage
See agent-log.txt. One-line honest summary here.

agent-log.txt template

# Agent Log — Project 9

**Agent used:** [Claude Code / other], [model if known]

## What I decided myself (the agent did NOT decide these)
- The resource and its fields (prayer request = { id, title, answered }).
- Which endpoints exist and which methods they support.
- The correct status code for each outcome.
- (Hard) The right-tool conclusion in MEMO.docx.

## Delegation log

### Task 1 — <what you asked the agent to do>
**Prompt / instruction:** ...
**What the agent did:** ...
**Where it was wrong / what I corrected:** ...
**Decision I kept for myself:** ...

### Task 2 — ...
...

## Honest summary
One paragraph: what fraction of the typing the agent did, what it got wrong,
and where your judgment was the thing that made the project correct.

Hints (Read Before You Begin)

  • Build it in the order the chapter taught it. Smallest server (§9.5) → routing (§9.6) → body reading with try/catch (§9.7) → assemble the full API (§9.8) → test with curl (§9.9). Each step runs on its own. Don’t write the whole thing and then debug a wall of code.
  • return after every response. The number-one Node API bug is falling through to a second res.end (“Cannot set headers after they are sent”). One request, one response, then return.
  • Wrap every JSON.parse of the body in try/catch. A public server will be sent garbage. Surviving it is a requirement, not a nicety — and it’s how you avoid the crash that takes down every connection at once.
  • Use curl -i for everything. It shows the status line. You cannot verify “correct status codes” without seeing them, and the body alone won’t tell you.
  • Decide your endpoints before you prompt the agent. Write the endpoint table in your README first, by hand. Then the agent fills in handlers for routes you designed. If you let the agent invent the API, you’ve handed it the one decision the project reserves for you — and your agent-log.txt will show it.
  • (Hard) setTimeout vs a busy loop is the whole point. setTimeout(cb, 5000) hands control back to the event loop, which keeps serving. A while loop on the clock holds the one thread hostage. Build both and watch the difference — that contrast is the lesson of the week.

What Mastery Looks Like (Beyond the Rubric)

A great Project 9 stays up. Throw malformed JSON at it, request ids that don’t exist, hit routes that aren’t there — it answers every one with the honest status code and never crashes. That resilience is the difference between a toy and a server.

A great Project 9 has status codes you can defend. Ask the author “why 404 here and not 400?” and they have an answer: “404 means the resource doesn’t exist; 400 means the request itself was malformed. The id was well-formed; there’s just no request with that id. So: 404.” That precision is the contract skill from Coding 2, wearing HTTP’s clothes.

A great Project 9 has an agent-log.txt that proves the human was the architect. The agent typed handlers; the human decided the handlers should exist, what each should return, and which status code was correct. A log that reads “I asked the agent to build a prayer API and it did” is a failing log — it means you handed away the one thing this project exists to teach. The log that earns the points reads like a senior directing a fast junior: here’s the endpoint I designed, here’s what I had it implement, here’s where it chose the wrong status code and I corrected it, here’s the decision I never delegated.

A great Hard tier felt the event loop. The author didn’t just read §8.7 — they watched /slow and /health get answered concurrently, watched /slow-blocking freeze everything, and wrote a memo that connects what they saw to the single-thread model and to a real tool choice. That is the right-tool thesis earned through measurement, which is the soul of this book.

Coach’s Note — The temptation in the first Phase 2 project is to let the agent build the whole thing in five minutes and call it done. It can. And you will have learned nothing, and Week 10 — the same API again, your judgment compared across two stacks — will expose you instantly. Treat Project 9 the way you’ll treat the next seven: you are the architect, the agent is the fast hands, and the log is where you prove the judgment was yours. The students who internalize that this week cruise the rest of Phase 2. The ones who phone it in pay for it at the final.


When You’re Done

  1. Start the server. Run every curl command in your README and confirm every status code by eye with -i.
  2. Throw garbage at it — malformed JSON, missing fields, unknown routes, non-numeric ids. Confirm it answers cleanly and stays up.
  3. (Hard) Run both slow endpoints and capture the contrast in your transcripts.
  4. Read your agent-log.txt. Does it honestly show you owning the design and the agent owning the typing? If a stranger read it, would they believe you were the architect?
  5. Commit and push. Submit the repo URL.
  6. Read Chapter 10. Next week you build this exact API again in FastAPI — and the choice between the two stacks becomes a decision you have felt from both sides.

A theological footnote. You named this thing a server, and the word is older than the machine. To serve is to wait to be of use — to answer what you are asked, as well as you can, and to stay standing for the next one who knocks. “Whoever would be great among you must be your servant.” The web runs on millions of these small, patient servants, each one answering a request it did not choose, returning an honest answer, and waiting for the next. You built one this week and you made it stay up through the bad requests as well as the good ones. That faithfulness under load — answering the difficult caller as truthfully as the easy one, never crashing, never lying about what happened — is a small picture of a large virtue. Build servers that serve.

See you next week.