What is Middleware?
Middleware is the central idea in Express — and once it clicks, the whole framework makes sense. A middleware is simply a function that runs during the request/response cycle, with access to req, res, and a next function that passes control to the next middleware. Requests flow through an ordered pipeline of these functions; each can inspect, modify, respond, or hand off. Routes, body parsers, auth, logging, error handling — in Express, everything is middleware.
The signature
function middleware(req, res, next) {
// 1. Do something with req / res
// 2. Then EITHER:
next() // pass control to the next middleware, OR
// res.send(...) // send a response and end the cycle
}The pipeline model
Picture the request passing through a series of functions in registration order. Each calls next() to continue. The first one to send a response stops the flow:
Incoming request
│
▼
[ logger ] ──next()──▶ [ express.json ] ──next()──▶ [ auth ] ──next()──▶ [ route handler ]
│ │
(no token? respond 401) (sends the response)
│
cycle ends earlynext() — the control-flow lever
const app = express()
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`) // runs for every request
next() // → continue to next middleware
})
app.get('/', (req, res) => {
res.send('Home') // sends response, cycle ends
})Passing errors with next(err)
Calling next() with an argument is special: it skips all remaining normal middleware and jumps straight to the error handler:
app.get('/users/:id', async (req, res, next) => {
try {
const user = await db.find(req.params.id)
if (!user) {
const err = new Error('Not found')
err.status = 404
return next(err) // → jump to error-handling middleware
}
res.json(user)
} catch (err) {
next(err) // forward unexpected errors too
}
})The types of middleware
Type | Scope | Registered with |
|---|---|---|
Application-level | Whole app |
|
Router-level | One router |
|
Built-in | Ships with Express |
|
Third-party | From npm |
|
Error-handling | Catches errors |
|
Each gets its own page: application-level, router-level, built-in, third-party, custom, and error-handling.
Why this design is powerful
Composability — build complex behavior from small, single-purpose functions.
Reusability — the same logging/auth middleware works across routes and projects.
Separation of concerns — cross-cutting concerns (auth, logging, parsing) live outside handlers.
Ecosystem — thousands of npm middleware packages plug straight in.