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 between an incoming HTTP request and the route handler. ```js app.use((req, res, next) => { console.log(`${req.method} ${req.url}`); next(); // pass to the next middleware or route }); ``` **Key point:** middleware runs in registration order. Skipping `next()` stops the chain immediately.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 the route handler, with access to `req`, `res`, and a `next()` function to pass control forward. ## Theory ### TL;DR - Airport security line: each checkpoint (middleware) inspects your bag (`req`), stamps your ticket (`res`), waves you through (`next()`). Any checkpoint can stop you. - Middleware runs in the order you register it. Order matters. - Call `next()` to pass control forward. Skip it and the request hangs. - Error middleware takes 4 args: `(err, req, res, next)`. Always place it last. - Shared logic across routes (logging, auth) goes in middleware. Business logic goes in the route handler. ### Quick example ```js const express = require('express'); const app = express(); // Logging middleware - runs on every request app.use((req, res, next) => { console.log(`${req.method} ${req.url}`); // GET /users next(); // pass control to the next function }); // Auth middleware - runs only on /users app.use('/users', (req, res, next) => { if (req.headers.authorization) { next(); // token found, proceed } else { res.status(401).send('Unauthorized'); // stops here } }); // Route handler - only reached if auth passes app.get('/users', (req, res) => { res.json({ users: ['Alice', 'Bob'] }); }); app.listen(3000); ``` `GET /users` with a valid auth header: logs the request, passes auth check, returns users. Without the header: 401, done. ### How the pipeline works Express builds a stack of middleware functions during app setup, stored as layers in `app._router.stack`. When a request arrives, Node's `http.Server` calls `app.handle(req, res)`, which walks the stack one layer at a time. Each layer executes its function and passes `next` as a dispatcher to advance to the next matching layer or route. Async middleware does not call `next()` automatically. You have to do it manually, or the request hangs waiting for a response that never arrives. I have seen this cause multi-hour debugging sessions in codebases where middleware was written by developers new to Express. ### Middleware types **Application-level** middleware runs on every request (or every request matching a path prefix) when registered with `app.use()`. **Route-level** middleware applies only to specific routes: `app.get('/path', middleware, handler)`. You can chain multiple middleware functions before the final handler. **Error-handling** middleware has exactly 4 parameters: `(err, req, res, next)`. Express recognizes the 4-arg signature and routes errors there when you call `next(err)`. **Built-in**: `express.json()`, `express.urlencoded()`, `express.static()` cover the most common needs without extra packages. **Third-party**: `morgan` for logging, `helmet` for security headers, `cors` for cross-origin requests, `express-rate-limit` for rate limiting. ### When to use - Cross-cutting concerns (logging, CORS, security headers): `app.use()` at the top of your file. - Route-specific checks (auth, request validation): pass middleware directly into the route definition. - Error recovery: `app.use((err, req, res, next) => {...})` placed after all routes. - Static file serving: `express.static('public')`. - Business logic belongs in route handlers, not middleware. ### next(), next('route'), and next(err) ```js next() // proceed to the next middleware or route handler next('route') // skip remaining middleware in this stack, jump to next route match next(err) // jump directly to the error handler ``` `next('route')` is a senior-level detail. It lets you write fallback routes: run some middleware, decide the current route should not handle this request, and skip to the next matching route definition. ### Common mistakes **Forgetting next() in async middleware** ```js // Wrong - request hangs forever app.use(async (req, res, next) => { req.data = await db.query('SELECT * FROM users'); // no next() call }); // Correct app.use(async (req, res, next) => { try { req.data = await db.query('SELECT * FROM users'); next(); } catch (err) { next(err); } }); ``` Express processes middleware synchronously. An async function returns a Promise immediately, but Express does not wait for it. The `await` resolves, but `next()` was never called. **Registering global middleware after routes** ```js // Wrong - logger never runs for /users app.get('/users', handler); app.use(logger); // Correct app.use(logger); app.get('/users', handler); ``` Routes match first. Any `app.use()` registered after a matching route is skipped for that route. **Calling next() twice** ```js // Wrong - triggers next middleware twice app.use((req, res, next) => { next(); next(); // second call corrupts the stack }); ``` This causes duplicate processing, duplicate log entries, and "headers already sent" errors. Call `next()` exactly once per middleware function. **Error handler placed before routes** ```js // Wrong app.use(errorHandler); app.get('/users', handler); // Correct - error handler is always last app.get('/users', handler); app.use(errorHandler); ``` **Wrong dependency order on req** If your middleware reads `req.body`, make sure `express.json()` runs first. Otherwise `req.body` is undefined and your middleware fails silently. ### Real-world usage - `morgan`: HTTP request logging in production APIs - `helmet`: sets security HTTP headers (used in over 1 million npm projects) - `passport.js`: authentication via `app.use(passport.initialize())` - `cors`: cross-origin resource sharing for frontend/backend setups - `express-rate-limit`: protects `/api` routes from abuse - `express.json()`: replaced the old `body-parser` package (built-in since Express 4.16) ### Follow-up questions **Q:** What is the difference between `app.use()` and `router.use()`? **A:** `app.use()` registers middleware globally on the application. `router.use()` scopes it to a sub-router, so it only runs for routes defined on that router. Useful when splitting a large API into separate modules. **Q:** Can middleware use async/await? What are the pitfalls? **A:** Yes. But you must call `next()` manually after async work finishes and wrap everything in `try/catch` to call `next(err)` on failure. Unhandled rejections crash the Node process in version 15+ and silently drop requests in older versions. **Q:** How does `next('route')` work? **A:** It skips all remaining middleware in the current route stack and jumps to the next matching route definition. Useful for conditional routing, such as checking a feature flag and falling through to a default handler if the flag is off. **Q:** What happens if you call `res.send()` and then `next()`? **A:** The response is already sent. `next()` advances the stack, but any middleware that tries to write to `res` again gets a "headers already sent" error. Add `return` before `res.send()` to prevent this. **Q:** How much does a large middleware stack affect performance? **A:** Very little. The synchronous loop through the stack takes roughly 1 microsecond per layer. The real bottleneck is async I/O, not stack depth. Measure with `clinic.js` if you suspect overhead. ## Examples ### Basic: request logging and auth gate ```js const express = require('express'); const app = express(); app.use(express.json()); // Logger - records method, url, and marks start time app.use((req, res, next) => { req.startTime = Date.now(); console.log(`--> ${req.method} ${req.url}`); next(); }); // Auth check for protected routes function requireAuth(req, res, next) { const token = req.headers.authorization?.split(' ')[1]; if (!token) return res.status(401).json({ error: 'Token required' }); req.user = { id: 42 }; // in real code: verify the JWT here next(); } app.get('/public', (req, res) => { res.json({ message: 'Anyone can see this' }); }); app.get('/private', requireAuth, (req, res) => { res.json({ message: 'Hello', userId: req.user.id }); }); app.listen(3000); ``` The logger runs on both routes. `requireAuth` runs only on `/private`. If the token is missing, the route handler never executes. ### Intermediate: async middleware with proper error handling ```js const fakeDB = { findUser: async (token) => { if (token === 'valid') return { id: 1, name: 'Alice', admin: true }; throw new Error('User not found'); } }; app.get('/profile', // Step 1: load user from DB based on token async (req, res, next) => { try { req.user = await fakeDB.findUser(req.query.token); next(); } catch (err) { next(err); // sends to error handler, not res.send() } }, // Step 2: check admin access (req, res, next) => { if (!req.user.admin) { return next(new Error('Access denied')); } next(); }, // Step 3: respond (req, res) => { res.json(req.user); } ); // Central error handler - must be last, must have 4 args app.use((err, req, res, next) => { console.error(err.message); res.status(500).json({ error: err.message }); }); ``` Without `next(err)` in the async middleware, an unhandled promise rejection crashes the process (Node 15+) or silently drops the request in older versions. The explicit `try/catch` with `next(err)` is the correct pattern. ### Senior: rate limiter with request context ```js const express = require('express'); const app = express(); // Attaches timing and user ID to every request object app.use((req, res, next) => { req.startTime = Date.now(); req.userId = req.headers['x-user-id'] || 'anonymous'; next(); }); // Simple in-memory rate limiter for /api routes const requestCounts = {}; app.use('/api', (req, res, next) => { const key = req.userId; requestCounts[key] = (requestCounts[key] || 0) + 1; if (requestCounts[key] > 100) { return res.status(429).json({ error: 'Rate limit exceeded' }); } next(); }); app.get('/api/users/:id', (req, res) => { const elapsed = Date.now() - req.startTime; console.log(`${req.userId} fetched user ${req.params.id} in ${elapsed}ms`); res.json({ id: req.params.id, name: 'Jane' }); }); app.listen(3000); ``` The first middleware enriches `req` with context that later middleware and handlers read freely. This is a common pattern in production APIs: one middleware populates `req.user`, `req.requestId`, `req.startTime`, and the rest of the stack uses those values without re-fetching anything.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.