Express.js Framework Explained — Routing, Middleware and Real-World Patterns
Express.js framework demystified: learn routing, middleware chains, error handling and real-world API patterns with runnable code and pro-level insights..
20+ years shipping production JavaScript and front-end systems at scale. Lessons pulled from things that broke in production.
- Express.js is a minimal Node.js web framework providing routing and middleware over raw HTTP
- Routing maps HTTP methods + URL patterns to handler functions, matched in registration order
- Middleware pipeline: functions with (req, res, next) that process requests sequentially
- Order is critical — specific routes before wildcards, error handlers last
- Performance overhead is ~50µs per request — negligible for 99% of APIs
- Biggest production mistake: missing
next()call in middleware causes silent request hang
Imagine Node.js is a blank kitchen — you have raw ingredients and appliances, but nothing is set up yet. Express.js is the fully organised kitchen: the counters are labelled, the knives are sharp, and there's a clear assembly line from raw ingredient to plated dish. When a customer order (an HTTP request) comes in, Express knows exactly which chef (route handler) should handle it, and it passes the order through a series of stations (middleware) — like the prep station, the grill, the plating area — before the finished dish (response) goes back out. Without Express, you'd be building all those stations from scratch every single time.
Every production Node.js application you've ever used — from GitHub's API to Shopify's backend services — needs to answer the same basic questions: Which URL maps to which logic? How do we authenticate a request before it reaches sensitive data? What do we send back when something breaks? Answering those questions by hand in raw Node.js means writing hundreds of lines of low-level HTTP boilerplate that has nothing to do with your actual business logic.
Express.js was created specifically to eliminate that boilerplate. It wraps Node's built-in http module in a clean, minimal layer that gives you routing, middleware, and response helpers without forcing any opinions about your database, template engine, or project structure. That deliberate minimalism is why Express has stayed the most downloaded Node.js framework for over a decade — it solves the core problem and gets out of your way.
By the end of this article you'll understand not just how to write Express routes and middleware, but why the middleware pipeline is designed the way it is, how to structure a real multi-route API, how to handle errors properly so they never leak stack traces to users, and the patterns senior engineers actually use in production. You'll leave with runnable code you can drop into a real project today.
What Express.js Actually Does — Routing, Middleware, and the Request-Response Cycle
Express.js is a minimal, unopinionated web framework for Node.js that wraps Node's HTTP server with a routing layer and a middleware pipeline. The core mechanic: every incoming request passes through a stack of middleware functions in order, and each function can modify the request/response objects, end the cycle, or pass control to the next function. This pipeline model is what gives Express its flexibility and power.
In practice, Express provides a simple API for defining routes (app.get, app.post) and mounting middleware (app.use). Routes are matched by path and HTTP method; middleware runs before route handlers and can handle cross-cutting concerns like logging, authentication, parsing, and error handling. The order of middleware registration matters — it defines the execution chain. Express does not enforce any project structure, ORM, or templating engine, which is why it's called unopinionated.
Use Express when you need a lightweight HTTP server for APIs, single-page application backends, or microservices. It excels in scenarios where you want full control over the request lifecycle without the overhead of a full-stack framework. In production, Express powers systems handling thousands of requests per second — but only when middleware ordering, error handling, and async patterns are done correctly.
How Express Routing Actually Works Under the Hood
A route in Express is a combination of an HTTP method, a URL pattern, and one or more handler functions. When a request comes in, Express walks through every registered route in the order you defined them and checks: does this method match, and does this URL match? The first route that matches wins.
This ordering matters enormously. If you define a wildcard route (app.get('*', ...)) before your specific routes, none of the specific routes will ever fire. This surprises a lot of developers because it looks like Express should be smarter about specificity — but Express is deliberately simple here. Order is explicit and predictable, which is actually a feature.
Route parameters (:userId) give you dynamic URL segments automatically parsed into req.params. Query strings (?page=2) live in req.query. The request body (for POST/PUT) lives in req.body — but only after you've attached the right body-parsing middleware, which is a common gotcha we'll cover shortly.
Express Router objects let you split routes across multiple files, each acting like a mini Express application. This is the pattern every real codebase uses — one router per resource (/users, /products, /orders), mounted on a base path in the main app file.
router.get('/me', ...) before router.get('/:userId', ...), otherwise the string 'me' gets parsed as a userId param and your dedicated route never fires.Using app.route() for Chained Route Handlers
Express provides a convenient method that allows you to define multiple HTTP method handlers for a single path without repeating the path string. This is especially useful when you have a resource URL that handles GET, POST, PUT, DELETE and you want to keep related handlers together.app.route()
The call returns an object on which you can chain app.route().get(), .post(), .put(), .delete(), etc. This eliminates path duplication and makes your route definitions more readable. You can also apply middleware to the entire chain by passing it before the method calls.
This pattern works identically on router objects ().router.route()
.all() method on a route chain applies middleware to all HTTP methods defined in that chain. This is perfect for authentication or logging that should run for every method on a resource.The Middleware Pipeline — Why It's the Heart of Every Express App
Middleware is any function with the signature (req, res, next). That next argument is the key — calling it tells Express to move on to the next function in the chain. Not calling it means the request stalls there forever, which is one of the most common bugs in Express apps.
Think of middleware as a conveyor belt. Every request starts at one end and travels through each function you've attached. Each function can read and modify the request or response objects, do async work like checking a database, or terminate the chain early by calling . This design means you can compose behaviour from small, focused functions rather than cramming everything into one giant handler.res.send()
There are three types of middleware you'll use constantly: application-level middleware (attached with ), router-level middleware (attached to a specific Router instance), and error-handling middleware (four arguments: app.use()err, req, res, next). The error handler is special — Express only routes to it when a previous middleware either calls next(error) or throws inside an async function that you've caught and forwarded.
Authentication is the canonical real-world middleware use case. You write it once, attach it to the routes that need protection, and every protected route automatically gets user identity on req.currentUser without repeating any logic.
async route handler and an error is thrown, Express 4.x does NOT automatically catch it — you must wrap in try/catch and call next(error). Express 5 (currently in beta) fixes this, but in Express 4 forgetting the try/catch causes the server to hang or crash silently.next(), send a response, or call next(error).Middleware Types in Express – Comparison Table
Express categorises middleware into five distinct types. Understanding each type helps you decide where to place your logic and how to structure your application. Below is a quick-reference comparison.
| Type | Scope | Registration | Use Case |
|---|---|---|---|
| Application-level | Entire app | or app. | Global tasks: logging, parsing, CORS, auth for all routes |
| Router-level | Specific router instance | or router. | Scoped middleware for a resource group (e.g., adminAuth for /admin) |
| Error-handling | Global | app.use(err, req, res, next) — exactly 4 params | Centralised error response, logging, formatting |
| Built-in | All requests | , , | JSON parsing, static file serving, URL-encoded body parsing |
| Third-party | Varies | app.use(require('morgan')('combined')) | Logging (morgan), security (helmet), compression (compression), rate limiting (express-rate-limit) |
Application-level middleware runs for every request unless scoped by a path. Router-level middleware only runs for requests that match the router's base path. Error-handling middleware must be registered last and requires four parameters. Built-in middleware comes with Express and covers common parsing needs. Third-party middleware extends functionality and is installed via npm.
Essential Third-Party Middleware for Express
While Express's built-in middleware covers basic parsing, real-world APIs need additional middleware for logging, security, performance, and rate limiting. The following four packages are considered essential by the community.
morgan — HTTP request logger. It automatically logs method, URL, status code, response time, and more. Use it in development for readable logs and in production for structured logging.
cors — Cross-Origin Resource Sharing. Without it, browsers block requests from frontends hosted on different origins. Configure it with an explicit allowed-origins list in production.
helmet — Security header setter. It adds 11 HTTP headers (X-Content-Type-Options, X-Frame-Options, Strict-Transport-Security, etc.) that protect against common web vulnerabilities.
compression — Response compression using gzip/deflate. It reduces JSON response size by 4–10x, significantly lowering bandwidth costs and improving load times.
Structuring a Production-Ready Express API — Beyond the Tutorial
A single app.js file works fine for demos. It becomes a maintenance nightmare at scale. The structure senior engineers actually use separates concerns into distinct layers: routes, controllers, services, and middleware — each with a single responsibility.
Routers handle URL mapping only. Controllers handle request/response logic. Services contain the business logic and data access — they know nothing about Express, which makes them independently testable. Middleware handles cross-cutting concerns like auth, logging, and rate limiting.
This separation isn't bureaucracy for its own sake. It means you can unit-test your service layer without spinning up an HTTP server. It means swapping your data layer (say, from a mock to a real database) requires changes in exactly one place. It means a new developer can open the routes/ folder and immediately understand every endpoint the app exposes.
Environment configuration should never be hardcoded. Use dotenv to load a .env file in development, and real environment variables in production. The Express app should read from process.env exclusively — no magic strings scattered through files.
Finally, always set NODE_ENV=production in prod. Express enables several performance and security optimisations automatically when it detects this, including disabling detailed error output and enabling response caching for static files.
app without calling listen() lets your test suite import the app and use supertest to fire HTTP requests without binding to a real port. This is standard practice in production codebases and a great signal to interviewers that you write testable Node.js applications.Request (req) and Response (res) API Quick Reference
Understanding the most commonly used properties and methods on req and res objects is crucial for effective Express development. Here's a quick reference table.
Request (req) Properties & Methods | Property / Method | Description | Example | |---|---|---| | req.params | Route parameters (e.g., :userId) | req.params.userId | | req.query | URL query string parameters | req.query.page | | req.body | Parsed request body (requires middleware) | req.body.email | | req.headers | HTTP headers object | req.headers['content-type'] | | req.method | HTTP method (GET, POST, etc.) | req.method === 'POST' | | req.url | Path portion of the URL | /api/users?role=admin | | req.path | Path only (no query string) | /api/users | | req.ip | Client IP address | ::ffff:127.0.0.1 | | req.cookies | Cookies (requires cookie-parser) | req.cookies.sessionId | | req.get(field) | Get a specific header value | req.get('Authorization') |
Response (res) Properties & Methods | Property / Method | Description | Example | |---|---|---| | res.status(code) | Set HTTP status code | res.status(404).json(...) | | res.json(body) | Send JSON response (auto-sets Content-Type) | res.json({ success: true }) | | res.send(body) | Send generic response (string, Buffer, etc.) | res.send('<h1>Hello</h1>') | | res.redirect(url) | Redirect to another URL | res.redirect('/login') | | res.render(view) | Render a template (requires view engine) | res.render('index', { title: 'Home' }) | | res.set(field, value) | Set a response header | res.set('X-Custom', 'value') | | res.cookie(name, value) | Set a cookie | res.cookie('sessionId', 'abc123') | | res.format(object) | Content negotiation | res.format({ 'text/plain': ... }) | | res.type(type) | Set Content-Type header | res.type('json') | | res.end() | End response without data | res.end() |
Mastering these properties and methods allows you to handle requests and construct responses efficiently.
res.status(), res.json(), res.send(), res.redirect() are the four essential response methods.Node.js vs Express.js: Key Differences
Raw Node.js’s http module can do everything Express does, but with far more boilerplate. Express is a thin layer that dramatically improves developer experience. Here’s a comparison to help you decide when to use each.
| Feature / Aspect | Raw Node.js http module | Express.js |
|---|---|---|
| Routing setup | Manual string matching with if/else chains | Declarative app.get/post/put/delete with URL pattern matching |
| URL parameters | Manual parsing from req.url using split or regex | Automatic via req.params, req.query, req.body |
| Middleware support | None built-in — must chain functions manually | First-class pipeline with app.use() and next() |
| Error handling | Try/catch in every handler, no central handler | Centralised 4-argument error middleware with next(err) |
| JSON body parsing | Manual buffer accumulation and JSON.parse | One line: app.use(express.json()) |
| Router modularity | Not supported natively | express.Router() for file-per-resource organisation |
| Learning curve | Low to start, high to scale | Low overall — opinionated just enough |
| Performance overhead | Zero — it's the baseline | Minimal — microseconds per request in benchmarks |
For most APIs, Express’s productivity gains far outweigh the tiny performance cost. Use raw Node.js only when you need absolute control, no third-party dependencies, or are building a low-level tool.
Error Handling Patterns That Don't Leak to Clients
Express error handling is a first-class concept, but it's easy to get wrong. The key insight: an error handler is just middleware with four parameters. Express identifies it by counting arguments — exactly 4. If you forget next (even if you don't use it), Express treats it as regular middleware and your errors go uncaught.
Your error handler should be registered LAST. Place it after all routes and other middleware. This ensures every error that bubbles up through the chain ends up there.
For async handlers in Express 4, you must manually catch errors and forward them. The standard pattern is a wrapper function that catches any rejected promise and calls automatically. Express 5 will do this natively, but until then, use next()express-async-errors or a custom wrapper.
Another common mistake: sending the full error object (including stack trace) to the client. Always check NODE_ENV before including stack traces. In production, send a generic message with a unique error ID that your logging system can correlate.
Testing Your Express API — A Practical Approach
Testing an Express API isn't just about unit-testing individual functions. You need integration tests that exercise the full middleware stack, route matching, controller logic, and error handling. The key enabler is separating app.js from server.js — your test file imports app (the Express instance) and uses supertest to make requests against it without binding to a real port.
Supertest works by wrapping your app in a server-like object that handles requests in-memory. This is fast and avoids port conflicts. You can test the exact middleware chain your users will hit, including body parsers, authentication, and error handling.
For unit tests, your controllers should be thin delegators to a service layer that has no Express dependencies. That way you can test business logic directly without HTTP. But for confidence before deployment, the integration test is king.
Important: your test setup must replicate the production middleware order exactly. If you conditionally add middleware based on environment, ensure your test config mirrors it.
listen()) to supertest, it creates an in-memory server using the http module. This means no port conflicts, no cleanup needed, and tests run in milliseconds. Always export your app from app.js and import in test files.Performance and Security Middleware Every Production API Needs
Express itself is lightweight, but a production API needs more than routes and json parsing. You need middleware for rate limiting, CORS, request compression, security headers, and request logging with correlation IDs.
Rate limiting prevents brute force and DDoS. express-rate-limit is the standard — configure it globally and optionally per-route.
CORS is mandatory if your API is consumed by a browser. Use the cors package and restrict origins, methods, and headers. Never allow * in production.
Compression with compression middleware reduces response size by up to 80% for text-based responses (JSON, HTML, CSS). Enable it early in the chain before routes to compress all responses.
Security headers via helmet set HTTP headers like X-Content-Type-Options, X-Frame-Options, and Strict-Transport-Security. It's a one-liner that prevents entire categories of attacks.
Finally, request logging with a unique correlation ID lets you trace a single request across your logs. Use uuid to generate an ID at the start of the middleware chain, attach it to req.correlationId, and include it in every log entry and error response.
- helmet sets security headers (prevents XSS, clickjacking, etc.)
- compression reduces bandwidth costs and improves load times
- cors restricts cross-origin requests to allowed domains only
- rate limiter blocks abusive traffic before it hits your routes
- correlation ID ties all logs together for debugging distributed requests
Practice Exercises to Solidify Your Express Skills
Theory is important, but coding builds fluency. These six exercises progress from basic CRUD to advanced middleware. Each is designed to reinforce concepts covered in this article.
Exercise 1: Build a CRUD REST API for a Resource Create an Express API for a TODO list with endpoints: - GET /todos — list all todos - GET /todos/:id — get single todo - POST /todos — create a new todo (body: title, completed) - PUT /todos/:id — update a todo - DELETE /todos/:id — delete a todo Use an in-memory array. Apply express.json() middleware. Return appropriate status codes (201 for create, 404 for not found).
Exercise 2: Implement Authentication Middleware Create a requireAuth middleware that checks for an Authorization header (format: Bearer <token>). Use a hardcoded token for testing. Protect the POST, PUT, DELETE routes from Exercise 1 so only authenticated users can modify todos.
Exercise 3: Implement Rate Limiter Middleware Write your own rate limiter middleware that tracks requests per IP using a Map. Limit to 10 requests per minute. Return 429 with a meaningful message when exceeded. Apply it to the todo API.
Exercise 4: Implement Global Error Handling Create an error handler middleware that catches all errors. Return a JSON response with status code and a unique error reference. For 500 errors, send a generic message. Log the error server-side. Test by throwing an error in one of your routes.
Exercise 5: Implement Logging Middleware Write middleware that logs each request with method, URL, timestamp, and response time. Add a correlation ID (use uuid) to each request and include it in logs and error responses.
Exercise 6: Implement Input Validation Middleware Create a configurable validate middleware that checks for required fields in req.body. For example, validate(['title', 'completed']) should return 400 if any field is missing. Apply it to the POST /todos route.
Each exercise builds on the previous. A complete solution would combine all six into a single well-structured Express API.
First Express.js Program — The Minimal Viable Server
You're about to write your first Express app. It's dead simple: import, instantiate, define a route, listen. But the simplicity is deceptive. Every production app started here, and every production incident I've seen traces back to something this basic done wrong.
The require('express') returns a function. Calling that function creates your application object — app. That's your entire server interface. The .get() method registers a route handler for GET requests at the root path. The .listen() starts the HTTP server on port 3000.
Notice there is no error handling, no middleware, no validation. That's fine for a hello world. But the moment you add real routes, you need middleware to parse bodies, handle CORS, and validate input. Don't ship this as-is to production. It's a starting line, not a finish line.
process.env.PORT || 3000.Applications of Express — Where It Shines and Where It Doesn't
Express is not a universal hammer. It's a sharp scalpel for specific cuts. Use it for REST APIs, real-time dashboards (with Socket.IO), and lightweight server-side rendered apps. It excels when you want fine-grained control over your middleware stack without a framework dictating your architecture.
Where Express fails? Heavy real-time streaming (use Fastify or native HTTP), graph-heavy APIs (consider Apollo Server), or anything requiring strict schema enforcement out of the box. Express gives you freedom — and freedom means you can shoot yourself in the foot.
Every senior dev has seen Express apps that started as a simple API and grew into a tangled mess of middleware spaghetti. The solution: enforce structure early. Group routes by domain, isolate business logic from HTTP handling, and never let a controller function exceed 20 lines.
userService.create()) and call it from the handler. Keeps testable code separate from HTTP plumbing.Express.js vs Other Frameworks — Why Not Just Use Everything?
Stop asking 'Which framework is best?' and start asking 'What problem am I solving?'. Express is unopinionated — you bring your own structure, your own ORM, your own validation. That's its superpower and its curse.
Compare to Fastify? Fastify is faster (benchmarks show 2-3x throughput) and comes with schema validation out of the box. But it forces you into a plugin architecture that can feel rigid. Express wins when you need to integrate with legacy systems or want maximum flexibility.
Compare to Koa? Koa uses async/await natively and eliminates callback hell. But its middleware cascading model (await ) confuses developers who learned Express's linear pipeline. Stick with Express if your team knows it. Switch to Koa if you're building from scratch and want cleaner async error handling.next()
Here's the rule: If your app is simple CRUD with predictable traffic, Express is fine. If you're processing thousands of requests per second, look at Fastify. If you're allergic to callbacks, try Koa. But don't rewrite everything — your technical debt won't care what framework it lives in.
Why Database Integration in Express Is Nothing Like That ORM Hype
You don't "integrate" a database into Express. You write a connection file, attach it to req, and move on. The framework cares about requests and responses, not your PostgreSQL pool. That's your job.
The WHY is simple: Express gives you req and res. The database is a side effect. If you block the event loop with a slow query, your entire API stalls. That's why you pool connections, use async middleware, and never, ever open a connection inside a route handler.
The HOW: create a db.js module that exports a promise-based pool. Import it in your route files. Use async route handlers with try/catch. Return 503 on connection failure, not a stack trace. Production APIs do this in under 20 lines of middleware. Anything more is architecture astronaut nonsense.
Stop Using res.render() — Here's How to Serve Views Without the Bloat
Template engines in Express are a legacy crutch for server-rendered HTML apps. If you're building an API, you don't need Pug, EJS, or Handlebars. You return JSON. Full stop.
But sometimes you have to render HTML — old-school SEO pages, admin dashboards, or email templates. The WHY: view engines do string interpolation on the server. That's all. They don't magically "integrate" with your data. You pass an object, it fills in the blanks, you send the string.
The HOW: configure your view engine once in app.js. Use res.render('template', { data }) instead of res.send() for HTML responses. Keep templates in a /views folder. And for the love of debugging, never put business logic in the template. If you need a conditional, do it in the route handler and pass a boolean.
res.send() with string interpolation. Treat templates as dumb formatters, never as control flow.What Express.js Actually Is — A Minimalist Routing and Middleware Framework
Express.js is a thin layer over Node.js's HTTP module. It doesn't abstract HTTP; it organizes it. Instead of writing raw request listeners with http.createServer((req, res) => {}), Express gives you a router and a middleware pipeline. The core value: it matches URLs to functions (routes) and lets you chain preprocessing steps (middleware) without nesting callbacks. Nothing more. Many tutorials claim Express is a 'web framework' — it's really just a routing library with middleware glue. You still handle raw response objects (res.end, res.json, res.set), parse body streams manually or via a library, and manage sessions yourself. Understanding this minimal scope prevents architectural mistakes: Express doesn't enforce structure, so you must impose it with folders, error boundaries, and environment handling. Miss this, and your 'Express app' becomes a tangled file of route handlers with no separation of concerns.
Prerequisites Before You Touch Express.js — Node.js Fundamentals You Must Master First
Express assumes you know Node.js core. Without it, you'll confuse async errors, misunderstand why middleware behaves differently, and fight req/res APIs. Required: arrow functions and closures (middleware captures req/res in scope), callbacks and Promises (Express 4 doesn't handle rejected Promises — you must catch them yourself), and the http module's response methods: res.end(), res.writeHead(), and res.setHeader(). Express exposes all of these. If you can't explain why res.json() calls JSON.stringify followed by res.end(), you'll misread stack traces. Also: know module.exports and require() for splitting route files. Finally, understand Buffer and streams — Express request bodies are streams, not strings. Jumping into Express without these leads to silent failures: routes that hang because res.send() is never called, or JSON parse errors from raw body data. Use console.log to trace every request if you're unsure. The investment in Node.js fundamentals pays back tenfold in debugging speed.
What is Express.js?
Express.js is a minimal and flexible web application framework for Node.js, designed to build web servers and APIs with astonishing efficiency. Rather than imposing a rigid structure, Express provides a thin layer of fundamental web application features — routing, middleware, and request/response handling — without obscuring the underlying Node.js capabilities. At its core, Express is a routing and middleware framework: incoming HTTP requests are passed through a stack of functions (middleware) that can modify the request, execute logic, or end the response cycle. This simplicity means you stay close to the metal, writing JavaScript that runs directly on Node's event loop. Express doesn't include batteries like ORMs or template engines by default; instead, it gives you the freedom to compose exactly what you need. It’s the foundation for countless production systems because it solves the 80% case — handling routes, parsing bodies, managing sessions — with zero boilerplate overhead. Understanding Express.js means understanding that it is not a full-stack framework but a surgical tool for HTTP abstraction. It powers giants like PayPal, Myntra, and even the Ghost blogging platform.
Express.js Jobs and Salary
Express.js expertise directly translates into high-demand backend and full-stack roles. Job titles include Backend Node.js Developer, Full-Stack JavaScript Engineer, and API Architect. Salaries reflect Express's ubiquity in production systems: in the United States, a mid-level Express developer earns between $110,000 and $145,000 annually, while senior roles command $150,000 to $190,000. Remote positions often pay at the higher end due to talent scarcity. Why such premium compensation? Express powers the API layer for startups and enterprises alike — companies like Stripe, Uber, and Walmart depend on Node.js backends. Employers pay for your ability to design performant REST/GraphQL APIs, integrate middleware securely, and avoid common Express pitfalls like unhandled rejections or memory leaks. The market favors developers who understand Express deeply rather than those who chase framework hype. Freelance rates range from $75 to $150 per hour for contract work. To maximize salary, pair Express with TypeScript, PostgreSQL, and cloud deployment skills (AWS/GCP). The rise of serverless and edge computing has only increased demand for Express-style HTTP handlers, making this skill future-proof.
Missing next() Causes Production API Timeout
next() call for valid tokens. Without next(), Express never moves to the route handler — the request stalls.next() call after successful authentication. Also add a global timeout middleware using req.setTimeout() as a safety net to prevent resource leaks.- Every middleware must call
next(), send a response, or forward an error. No exceptions. - Use a global request timeout to catch hung connections in production.
- Test middleware chains with integration tests that verify response delivery.
app.use(express.json()) is registered before any route that reads the body. Also verify Content-Type: application/json header in the request.next() calls. Use console.log or a request ID middleware to trace execution flow.next(error). Verify the error handler has exactly 4 parameters (err, req, res, next).app.use(express.static('public')) and ensure the directory exists. Verify the path is relative to where the Node process runs.curl -X POST http://localhost:3000/api -H 'Content-Type: application/json' -d '{"key":"value"}'node -e "console.log(require('express').json)" to verify express.json is availableapp.use(express.json()) as the first middleware in app.jsKey takeaways
next(), send a response, or forward an error. A missing next() is a silent, hard-to-debug hang.listen() is what makes your Express application properly testable with tools like supertest.next even if unused breaks the entire error handling chain.Common mistakes to avoid
5 patternsForgetting express.json() before routes that read req.body
express.json()) at the top of your app.js, before any route definitions. Without it, Express never parses the request body stream.Not calling next() inside middleware
next(), call res.send()/res.json() to end the chain, or call next(error) to forward to the error handler. There is no fourth option.Placing the global error handler before routes
app.use() and route definitions. Express only uses them as a fallback when nothing earlier handled the request.Using async route handlers without catching errors in Express 4
Sending stack traces to clients in production
Interview Questions on This Topic
What is the difference between app.use() and app.get() in Express, and when would you use one over the other?
app.get() only matches GET requests to an exact or parameterised URL. Use app.use() for middleware like logging, auth, or CORS that should run for multiple methods. Use app.get() for route handlers that accept only GET requests. Mixing them up can cause unexpected behaviour: using app.get() where app.use() is intended will only apply to GET requests, and vice versa.Frequently Asked Questions
20+ years shipping production JavaScript and front-end systems at scale. Lessons pulled from things that broke in production.
That's Node.js. Mark it forged?
20 min read · try the examples if you haven't