What you'll learn
A maintainable API separates transport, routing, validation, domain behavior, and error translation. REST gives resources predictable methods, representations, and status codes.
By the end of this lesson, you'll be able to:
- Design resource-oriented endpoints
- Build middleware boundaries
- Return consistent errors and pagination
Core mental model
Use this decision table as a compact reference. Focus on what each tool means and when it earns its place in production code.
| Concept | What it means | Decision rule |
|---|---|---|
| Route | Method and path mapped to one use case | Name resources with nouns and use HTTP semantics |
| Middleware | Ordered request/response cross-cutting step | Reserve for shared concerns, not hidden domain logic |
| Problem response | Stable machine-readable error shape | Return status, code, message, and safe details |
Professional workflow
Build the behavior in small, observable steps. Each step should leave something you can inspect or test.
- Describe the HTTP API boundary: inputs, outputs, state, timing, and expected failures.
- Implement the smallest correct path with names that expose intent.
- Add edge cases and failure handling before introducing abstractions.
- Verify behavior with realistic data and one deliberately adversarial example.
- Refactor only after the observable behavior is protected.
Make behavior observable
Guided code lab
Build a small Node JSON route
Transport concerns remain visible: method, URL, status, headers, and body.
import { createServer } from "node:http";
const server = createServer((request, response) => {
if (request.method === "GET" && request.url === "/api/courses") {
response.writeHead(200, { "Content-Type": "application/json" });
response.end(JSON.stringify([{ id: "javascript", title: "JavaScript" }]));
return;
}
response.writeHead(404, { "Content-Type": "application/json" });
response.end(JSON.stringify({ code: "not_found", message: "Route not found" }));
});
server.listen(3000);Validate at the route boundary
The transport layer translates input into a trusted command before invoking domain behavior.
function parseCourse(body) {
if (!body || typeof body.title !== "string" || body.title.trim() === "") {
throw new TypeError("title is required");
}
return { title: body.title.trim(), published: Boolean(body.published) };
}Production practice
Contract
Make the HTTP API boundary explicit with validated inputs, structured outputs, owned resources, and stable failures.
Verification
Exercise normal work, invalid input, dependency failure, concurrency, and graceful cleanup in automated tests.
Operations
Use structured logs, health signals, timeouts, and configuration that can change without editing source code.
Common failure mode
Independent workshop
Build a course-notes REST API with clean transport and domain layers.
Your finished workshop must include:
- CRUD resource routes
- Validated bodies and consistent errors
- Cursor or page pagination with integration tests
Definition of done
Recap & quick check
Key takeaways
- Routes map transport to use cases
- Validation belongs at boundaries
- Status codes carry semantics
- Consistent errors simplify clients
Quick check
1. Which method normally creates a subordinate resource?
2. Where should request validation occur?
3. What status fits a missing resource?
Keep the workshop. Later modules deliberately build on these decisions, so each exercise can become part of your final portfolio architecture.