█
LastWrite
  • > Curriculum
  • > Pricing
  • > For Educators
  • > About
  • > Contact
Log InGet Started

Questions, concerns, bug reports, or suggestions? We read every message, write to us at [email protected].

More ways to reach us →
LastWrite

Structured computer science lessons for aspiring developers and security professionals.

[email protected]

(201) 785-7951

Mon–Fri, 9 AM–5 PM EST

Learn

  • Curriculum
  • Pricing

Company

  • About
  • For Educators & Schools
  • Contact Us

Legal

  • Terms of Service
  • Privacy Policy
© 2026 LastWrite. All rights reserved.
Curriculum/Programming Languages/Node.js/REST API Design with Express / Fastify
50 minIntermediate

REST API Design with Express / Fastify

After this lesson, you will be able to: Design REST APIs: routing, middleware, error handling, request validation, consistent response shape.

Same patterns whether Express or Fastify. Knowing them = production-grade APIs.

Prerequisites:Building HTTP Servers

Routing + middleware

Composition is the model.

python
import express from "express";
const app = express();
// Middleware: runs for every request that matches
app.use(express.json());
app.use((req, _res, next) => {
console.log(req.method, req.url);
next();
});
// Mount a router (modular routes)
import userRoutes from "./routes/users.js";
app.use("/api/users", userRoutes);
// routes/users.js
import { Router } from "express";
const router = Router();
router.get("/", listUsers);
router.get("/:id", getUser);
router.post("/", requireAuth, createUser);
export default router;

Error handling

One central handler for consistent responses.

tsx
// Errors thrown in handlers bubble to this middleware (Express 5 auto-awaits)
app.use((err, _req, res, _next) => {
if (err instanceof z.ZodError) {
return res.status(400).json({
error: { code: "validation", details: err.flatten() },
});
}
if (err instanceof NotFoundError) {
return res.status(404).json({ error: { code: "not_found" } });
}
console.error(err);
res.status(500).json({ error: { code: "internal" } });
});

Consistent response shape

Pick a shape and stick to it: `{ data, error }` or `{ ok, data, error }`. Wrong: random `{ users: [...] }` here, `{ result: ... }` there, raw arrays elsewhere. Clients write less defensive code when the shape is predictable.

Validation at the boundary

Use Zod for request bodies, query strings, path params.

python
import { z } from "zod";
const ListQuery = z.object({
limit: z.coerce.number().int().positive().max(100).default(20),
cursor: z.string().optional(),
});
app.get("/users", async (req, res) => {
const q = ListQuery.parse(req.query); // throws on bad input; caught by central error handler
const users = await db.user.findMany({ take: q.limit, cursor: q.cursor ? { id: q.cursor } : undefined });
res.json({ data: users });
});

Common mistakes only experienced API devs avoid

Trusting req.body without validation. Always parse. Inconsistent response shapes. Clients suffer. Returning stack traces. Information leak. No request ID. Logs become un-traceable. Add `req.id = crypto.randomUUID()` early.

💡 REST design has its own subtrack

This lesson covers REST inside a Node.js server. The framework-independent design rules (resource naming, versioning, pagination, filtering, idempotency, error envelopes, OpenAPI documentation, and GraphQL vs REST tradeoffs) live in the Programming Languages > REST APIs subtrack. Take it if you want to design APIs that other teams can consume without asking you questions.

Quick Check

Why validate request bodies at the boundary even when TypeScript is strict?

Pick the cleanest answer.

Sign in and purchase access to unlock this lesson.

Sign in to purchase
←Building HTTP Servers: raw http → Express → Fastify
Back to Node.js
Authentication in Node→