Suggest an editImprove this articleRefine the answer for “What is middleware in Express.js and how does it work?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Middleware** in Express.js is a function with access to `req`, `res`, and `next` that runs during the request-response cycle. Express chains middleware in registration order: call `next()` to continue, send a response to end the cycle. Any property set on `req` in one middleware is available in all downstream handlers. **Key point:** error middleware requires four arguments and must be registered last.Shown above the full answer for quick recall.Answer (EN)Image**Middleware** in Express.js is a function that sits between an incoming HTTP request and your route handler, with access to the `req` object, the `res` object, and a `next` callback. ## Theory ### TL;DR - Think of middleware as airport security checkpoints: every request walks through them in registration order, and each checkpoint can inspect, modify, or stop the request before handing it off. - Core mechanic: call `next()` to continue the chain, send a response to end it. - Use for logic that applies to many routes (logging, auth, CORS). For single-route logic, a direct handler is simpler. - Error middleware takes four arguments `(err, req, res, next)` and must be registered after all other middleware. ### Quick example ```javascript const express = require('express'); const app = express(); // Middleware 1: logs request time app.use((req, res, next) => { console.log('Request at:', new Date().toISOString()); next(); }); // Middleware 2: attaches user data to req app.use((req, res, next) => { req.user = { id: 123 }; next(); }); // Route handler sees the modified req app.get('/', (req, res) => { res.json({ message: 'Hello', userId: req.user.id }); // Output: { message: 'Hello', userId: 123 } }); app.listen(3000); ``` Both middleware functions run before the route handler. The second one attaches `req.user`, so the handler reads it without needing to know how it got there. That shared `req` object is the whole point of the pattern. ### How Express chains middleware Every `app.use(fn)` pushes `fn` onto an internal stack array. When a request arrives, Express iterates that array in registration order, calling each function. Each function decides what happens next: call `next()` to continue, call `res.send()` to end the cycle, or call `next(err)` to jump to error handling. The `req` and `res` objects travel through the entire chain unchanged in identity. Any property you attach in middleware 1 is still there in middleware 5. That is how auth middleware can set `req.user` and every downstream handler can read it without any extra work. ### When to use - Request logging for all routes: register once with `app.use()` at the top of the file. - JWT verification before protected routes: pass the middleware function directly to the route or to a router group. - Body parsing: `app.use(express.json())` runs before any handler sees the request body. - CORS headers: middleware that adds headers and calls `next()`, or returns early for `OPTIONS` requests. - Async error catching: four-argument error middleware registered at the very end. Skip middleware when the logic applies to exactly one route with no reuse potential. A direct handler keeps things obvious. ### How the stack works internally Node.js passes each HTTP request to a single `http.ServerRequest` listener. Express attaches itself there and, on each request, walks its internal stack synchronously via `next()` calls. Each call advances the pointer to the next item in the array. Blocking code stalls every request because Node is single-threaded. If you want to understand why that matters at scale, the [event loop](/questions/event-loop) article covers it in detail. Express 5 (release candidate as of 2024) catches rejected promises from async middleware automatically. In Express 4, you must wrap async code in `try/catch` and call `next(err)` manually. Most production codebases I have seen still run Express 4, so that pattern matters and comes up in almost every senior interview about Express. ### Common mistakes **Calling `next()` twice** ```javascript // Wrong app.use((req, res, next) => { next(); next(); // triggers downstream middleware a second time }); ``` The second call re-invokes the next function in the stack. This causes duplicate log entries, double database queries, and "headers already sent" errors. Call `next()` exactly once, or not at all if you are sending a response. **Registering error middleware too early** ```javascript // Wrong: logger never runs when errors occur app.use(errorHandler); app.use(logger); ``` Express identifies error middleware by its four-argument signature. Registering it early means it catches errors before the rest of the chain runs. It must be last. **Modifying `req` or `res` after sending a response** ```javascript app.use((req, res, next) => { res.send('Done'); req.foo = 'bar'; // silently ignored next(); // no effect }); ``` After `res.send()`, the response is committed. Further changes to `req` or `res` are lost without any error. Check `res.headersSent` if the flow is unclear. **Blocking I/O inside middleware** ```javascript // Wrong app.use((req, res, next) => { const data = fs.readFileSync('/large-file'); // holds all requests next(); }); ``` Use `fs.promises.readFile` with `await` instead. Sync I/O in middleware is one of the most common performance problems in Express apps. **Missing `try/catch` in async middleware (Express 4)** ```javascript // Wrong in Express 4 - unhandled rejection, error handler skipped app.use(async (req, res, next) => { await fetchUser(); // if this rejects, nothing catches it next(); }); // Correct app.use(async (req, res, next) => { try { await fetchUser(); next(); } catch (err) { next(err); // routes to the error handler } }); ``` Unhandled async rejections in Express 4 print a warning but skip the error handler entirely. If you are not sure how async/await errors propagate, the [async/await](/questions/async-await) article walks through the mechanics. ### Real-world usage - `express.json()`: built-in middleware that parses JSON request bodies before handlers see them. - `helmet`: `app.use(helmet())` sets a set of security headers in one line. Used in the majority of production Express apps. - `morgan`: `app.use(morgan('combined'))` for structured HTTP request logging. - `passport.authenticate('jwt')`: auth middleware that verifies tokens and populates `req.user`. - Mongoose pre/post hooks: the same conceptual pattern at the schema level, but outside Express itself. ### Follow-up questions **Q:** What is the execution order when both `app.use()` and router-level middleware are present? **A:** App-level middleware runs first in registration order, then router-level, then route-specific handlers. Within each level, registration order determines execution order. **Q:** How does Express 4 handle async errors differently from Express 5? **A:** Express 4 requires manual `try/catch` with `next(err)`. Express 5 catches rejected promises from async middleware automatically, removing the need for wrappers. **Q:** Can middleware access `req.params`? **A:** It depends on registration. Top-level `app.use()` without a path pattern gets `req.params` as an empty object. Route-specific middleware and router middleware receive the params defined in the path. **Q:** How do you skip remaining middleware without sending a response? **A:** Call `next('route')` to skip to the next matching route handler. There is no way to skip silently without either sending a response or using `next('route')`. **Q (senior):** In a clustered Node.js setup, does state stored on `req` survive across workers? **A:** No. `req` lives only within a single request-response cycle on a single worker process. For data that needs to persist across requests or workers, use a session store backed by Redis or a database. Treating middleware state as stateless is the correct model for any production deployment. ## Examples ### Logging and request enrichment ```javascript const express = require('express'); const app = express(); app.use((req, res, next) => { req.startTime = Date.now(); console.log(`[${req.method}] ${req.url}`); next(); }); app.use((req, res, next) => { req.requestId = Math.random().toString(36).slice(2); next(); }); app.get('/status', (req, res) => { res.json({ requestId: req.requestId, elapsed: Date.now() - req.startTime, }); }); app.listen(3000); ``` Two middleware functions add data to `req`. The handler reads both values without caring where they came from. This separation keeps handlers focused on one job and makes each middleware independently testable. ### Auth and CORS in production ```javascript const express = require('express'); const app = express(); // CORS middleware app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Headers', 'Authorization, Content-Type'); if (req.method === 'OPTIONS') return res.sendStatus(200); next(); }); // JWT auth middleware const authenticate = (req, res, next) => { const token = req.headers.authorization?.split(' ')[1]; if (!token) return res.status(401).json({ error: 'No token' }); // Production: jwt.verify(token, process.env.SECRET, callback) req.user = { id: 1, role: 'admin' }; // simulated next(); }; app.get('/protected', authenticate, (req, res) => { res.json({ data: 'Secret', user: req.user }); }); app.listen(3000); ``` CORS middleware handles preflight requests and ends the cycle there. Auth middleware is passed only to routes that need it. The `return` before `res.status(401)` prevents `next()` from also being called after the response is sent. ### Async error handling with a wrapper ```javascript const express = require('express'); const app = express(); // Helper that wraps async handlers for Express 4 const asyncHandler = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next); app.use( asyncHandler(async (req, res, next) => { const user = await fetchUserFromDB(req.headers['x-user-id']); if (!user) return res.status(404).json({ error: 'Not found' }); req.user = user; next(); }) ); app.get('/me', (req, res) => { res.json({ user: req.user }); }); // Error handler - always last app.use((err, req, res, next) => { console.error(err); res.status(500).json({ error: 'Internal error' }); }); app.listen(3000); ``` The `asyncHandler` wrapper catches any rejected promise and forwards it to `next`, routing it to the error handler. This pattern is standard in Express 4 codebases. Express 5 removes the need for the wrapper entirely.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.