Web Frameworks: Express & Fastify
Express and Fastify are the two dominant Node.js web frameworks. Both map URLs and verbs to handlers, but they differ in philosophy: Express is minimal and imperative with a giant ecosystem, while Fastify is schema-driven, plugin-based and built for performance and validation.
What is this?
Web frameworks abstract raw HTTP handling behind a router and a middleware pipeline. Express wraps Node's http module with route matching, middleware chaining and helper methods, and its plugin ecosystem covers nearly every need. Fastify adds schema-based request validation, a decorator system, encapsulated plugins and benchmark-leading throughput, while keeping a familiar API.
Why do you need it?
- Routing by method and path replaces manual URL parsing, cutting boilerplate and bugs.
- Middleware chains let you compose cross-cutting concerns — logging, auth, parsing — in order.
- Request/response helpers (res.json, res.status) and error handling make handlers small and readable.
- Both frameworks have huge communities, docs and plugins, so production concerns (validation, CORS, OpenAPI) are a package install away.
Mental model
An airport security line
Middleware in a web framework
Each passenger (request) walks through stations in order: ID check (auth), baggage scan (body parsing), metal detector (rate limiting) — until they board (the route handler). Any station can stop the line (respond early), and the order matters.
How it works
- The framework receives the raw request and builds a Request and Response abstraction.
- A stack of middleware runs in registration order; each middleware can modify the request, respond early, or call next() to continue.
- The router matches the method and path pattern (including params like /users/:id) to a handler.
- The handler produces a response via res.send/json/status, which the framework serializes and returns.
- Errors thrown anywhere bubble to an error-handling middleware that formats a consistent error response.
Request flow through middleware, router and handler in Express/Fastify.
Key commands
npm install expressInstall Express into your project.
added 66 packages in 2s
npm install fastifyInstall Fastify.
added 20 packages in 1s
npx express-generator my-appScaffold a full Express project with a folder structure.
create : my-app\ncreate : my-app/package.json\n...
fastify.get('/x', handler)Register a route with Fastify's fluent API (works in any file that has the instance).
node --watch app.jsRun the framework app and restart on file changes during development.
Examples
A REST API with Express and one with Fastify
// Expressconst express = require("express")const app = express()app.use(express.json()) app.get("/users/:id", (req, res) => { res.json({ id: req.params.id, source: "express" })}) app.listen(3000, () => console.log("express on :3000")) // Fastifyconst fastify = require("fastify")({ logger: true }) fastify.get("/users/:id", { schema: { params: { type: "object", required: ["id"], properties: { id: { type: "string" } } } },}, async (request, reply) => { return { id: request.params.id, source: "fastify" }}) fastify.listen({ port: 3001 })Both register a GET route for /users/:id. Express uses app.use(express.json()) to parse bodies and res.json to send. Fastify inlines a JSON Schema for the params so requests are validated automatically, and returning an object from an async handler is all it needs to serialize a response — no res.json call required.
In production
- Express powers public-facing REST and GraphQL APIs across startups and enterprises, plus BFFs for web frontends.
- Fastify is a common choice for high-throughput microservices where latency per request matters.
- Both frameworks serve as the core for CLI-generated projects and serverless adapters (serverless-http, @fastify/aws-lambda).
- Companies expose OpenAPI docs from Fastify schemas or Express via swagger-jsdoc to keep clients in sync.
Best practices
- Keep handlers thin: parse/validate in middleware, do work in services, format in handlers.
- Register JSON body parsing and CORS before any route that needs them.
- In Express, use express.json() for bodies and a centralized error handler with four arguments.
- In Fastify, define JSON Schemas on every route so validation and serialization are automatic.
- Structure Fastify apps as plugins with fastify-plugin for encapsulation and testability.
Common mistakes
- Forgetting next() or returning without responding, which hangs the request until timeout.
- Registering middleware after routes, so routes never pass through it.
- In Express, using app.use with a path that over-matches (e.g. app.use('/user') also matches /userx).
- Trusting req.body before a body parser is registered, leaving it undefined.
Common issues
Cannot read properties of undefined (reading 'name') / req.body is undefinedThe body parser is missing or registered after the route.
Add app.use(express.json()) before your routes in Express.
FastifyError: Fastify is already listeninglisten() was called twice, usually via a test helper and the app entry point.
Guard the server start (if (require.main === module)) so tests can inject their own.
FST_ERR_VALIDATION: params/id is requiredA request arrived that does not match the route schema.
Check the request path and that the schema's required fields match the route params.
Debugging tips
- Add request logging early — Fastify has { logger: true } built in; Express needs morgan.
- A handler that never responds usually means a missing next() or an await that rejected and was unhandled.
- Use NODE_ENV=development and stack traces from the error handler to see the failing middleware chain.
- Reproduce with a single curl request and -v to see exactly what the server received and returned.
Interview checkpoint
Hands-on lab
- Scaffold a minimal Express app with JSON body parsing
- Build a CRUD endpoint for an in-memory store
- Replicate the API with Fastify including a JSON Schema
- Compare validation, logging and error responses
Ship the same API on Express and Fastify
35 min · intermediate
Quick quiz
What is the correct order for Express middleware that parses JSON bodies?
In Fastify, returning an object from an async handler automatically sends it as a JSON response.
Which framework validates request payloads against JSON Schema by default?
Cheat sheet
app.use(express.json())Parse JSON bodiesapp.get('/users/:id', h)Route with a paramres.json(obj)Send JSONnext(err)Forward to error handlerfastify.get('/x', opts, handler)Route with schema{ logger: true }Enable request loggingreply.code(201).send(obj)Status + payloadfastify.register(plugin)Encapsulated plugin