Express.js
Express.js is a minimal, unopinionated web framework for Node.js. It provides routing, middleware, and request/response helpers on top of Node's built-in HTTP server — and deliberately little else, leaving database access, validation, and project structure up to you.
That minimalism is why Express remains the most widely deployed Node.js framework: it is easy to learn, has a massive middleware ecosystem, and nearly every Node.js tutorial, hosting platform, and API example assumes it. If you understand Express, you can read most Node.js server code in the wild.
TL;DR
- Express is a thin layer over Node's HTTP server: routes map URLs to handler functions, middleware runs on every request in order.
- Everything is middleware — auth, logging, body parsing, and even routing are functions with the signature
(req, res, next). - Handlers either end the response (
res.json,res.send) or pass control withnext()— forgetting both hangs the request. - Use
express.Router()to split routes into modules as the app grows. - Centralize error handling in a single 4-argument error middleware registered last.
- Express 5 finally forwards rejected promises from async handlers to the error middleware automatically.
Quick Example
A complete REST API with JSON parsing, a route parameter, and error handling:
Core Concepts
Middleware
Middleware functions run in the order they're registered and can inspect or modify the request, end the response, or pass control to the next function. This single abstraction covers logging, auth, parsing, compression, and CORS.
The rules to remember:
- Order matters — middleware registered first runs first.
- A middleware must call
next()or send a response. Doing neither hangs the request; doing both causesERR_HTTP_HEADERS_SENT. next(err)skips remaining middleware and jumps to the error handler.
Routing
Routes match an HTTP method plus a URL pattern. Path segments prefixed with : become parameters:
express.Router() groups related routes into mountable modules:
Request and Response
Async Handlers and Errors
The classic Express footgun: in Express 4, a rejected promise inside a handler is not caught — the request hangs and the error is swallowed.
Express 5 forwards rejected promises to the error middleware automatically, so the first version above is safe. If you're on Express 4, either wrap handlers in try/catch, use a tiny helper, or upgrade.
Project Structure
Express doesn't impose one, but this layered layout scales well and keeps handlers thin:
Separating app.js from server.js lets tests import the app without opening a port — Supertest can drive it directly.
Best Practices
Validate Input at the Edge
Never trust req.body. Validate against a schema before it reaches business logic:
Harden the Basics
Add rate limiting on auth and write endpoints, and serve everything over HTTPS.
Centralize Error Responses
One error middleware, registered after all routes, decides what clients see:
Don't Block the Event Loop
Node runs your handlers on a single thread. CPU-heavy work (image processing, crypto, large JSON transforms) freezes every request. Push that work to background jobs or worker threads.
Express vs Alternatives
Express trades raw speed and built-in structure for simplicity and the largest middleware ecosystem. For most CRUD APIs the framework is never the bottleneck — the database is.
Common Mistakes
Forgetting to Send or Forward
Registering Middleware After Routes
Sending Two Responses
FAQ
Is Express still worth learning?
Yes. It remains the most used Node.js framework by a wide margin, most Node.js documentation and middleware target it, and frameworks like NestJS build on top of it. Its concepts (routing, middleware chains) transfer directly to nearly every other web framework.
What's the difference between Express 4 and Express 5?
Express 5 is a long-awaited modernization: rejected promises in async handlers are forwarded to error middleware automatically, some deprecated APIs were removed, and path matching was updated. Migration from 4 is mostly mechanical.
Should I use Express or Fastify?
Fastify is faster at raw JSON throughput and has built-in schema validation; Express has the bigger ecosystem and more familiarity. For most applications either is fine — pick Fastify for performance-sensitive JSON APIs, Express for maximum compatibility.
How do I serve static files?
app.use(express.static("public")) serves files from the public directory. In production, prefer a CDN or Nginx in front of Node for static assets.
How do I structure a large Express app?
Split routes into express.Router() modules, keep business logic in framework-agnostic service files, and reserve controllers for request/response translation. See the project structure section above.
Related Topics
- Node.js — The runtime Express runs on
- REST API Design — Resource modeling and HTTP semantics
- API Design — Contracts, versioning, and conventions
- Authentication — Sessions, tokens, and middleware-based auth
- Rate Limiting — Protecting endpoints from abuse
- API Testing — Testing Express apps with Supertest
- Error Handling — Patterns beyond the error middleware