Servers and environment
Build a small JSON server
Combine routing, request bodies, status codes, environment variables, file storage, and graceful shutdown in one final project.
8 minute lesson
Build a small notes API using the built-in HTTP module.
Define a small contract:
GET /notes -> 200 with { "notes": [...] }
POST /notes -> 201 with the stored note
Every other path returns 404. A known path with another method returns 405 and Allow: GET, POST.
Read request bodies with a size limit. A client controls how much data it sends and how slowly it sends it. Reject an oversized body with 413, malformed JSON with 400, and a body whose text is missing or blank with 400. Check the request content type before treating bytes as JSON. Configure header and request timeouts as well—a byte limit alone does not stop a client that sends a tiny body extremely slowly.
Do not send a success response before durable application state has been updated. Load and validate notes.json before the server starts listening. Serialize modifications through one in-process queue so two POST handlers cannot both read the same snapshot and lose one update.
Persist each new snapshot safely:
- serialize the complete validated array
- write a unique temporary file in the same directory
- close it and apply any durability flush your requirements need
- rename it over the destination
- send
201only after the rename succeeds
Atomic rename prevents a partial JSON file, but it does not coordinate several Node processes. Use a single writer, a lock designed for the platform, or a database if multiple instances must update the same data.
Choose and validate the port at startup:
const port = Number.parseInt(process.env.PORT ?? '3000', 10)
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error('PORT must be an integer from 1 to 65535')
}
Keep internal errors out of API responses. Log the error and its cause on the server, then return a generic 500 if headers have not been sent.
On SIGTERM, mark readiness false, call server.close() to stop accepting connections, wait for active requests and the write queue, then close remaining resources. Add a final deadline so a stalled client cannot block deployment forever.
Your action is to build the server and automate tests for malformed and oversized JSON, an unknown path, a wrong method, two concurrent POST requests, a restart, a storage failure, and a termination signal during a slow request. For every case, assert the status and the state visible after restart.
Lesson completed