What Is Middleware
Middleware is one of Express’s most powerful concepts. It’s the mechanism that lets you build a pipeline of reusable functions — logging, authentication, validation, CORS — that process every request before it reaches your route handlers.
The middleware pipeline
Section titled “The middleware pipeline”Every request that arrives at your Express server passes through a pipeline of functions before reaching a route handler. Each function in the pipeline can:
- Execute any code
- Modify
reqorres - End the request by sending a response
- Call
next()to pass to the next function in the pipeline
Request → [logger] → [cors] → [body-parser] → [auth] → route handler → ResponseThe (req, res, next) signature
Section titled “The (req, res, next) signature”Middleware functions take three arguments:
import { Request, Response, NextFunction } from 'express'
function myMiddleware(req: Request, res: Response, next: NextFunction) { // Do something with the request console.log(`${req.method} ${req.path}`)
// Pass to the next middleware or route handler next()}If you don’t call next(), the request hangs — no response is ever sent.
If you call next(error) with an argument, Express skips to error handling middleware.
Registering middleware
Section titled “Registering middleware”Use app.use() to register middleware:
// Applies to ALL routesapp.use(myMiddleware)
// Applies only to routes starting with /apiapp.use('/api', myMiddleware)
// Applies only to one specific routeapp.get('/posts', myMiddleware, routeHandler)Middleware registered with app.use() runs in the order it’s registered. Order matters.
A concrete example: request timer
Section titled “A concrete example: request timer”import { Request, Response, NextFunction } from 'express'
function requestTimer(req: Request, res: Response, next: NextFunction) { const start = Date.now()
// Listen for when the response finishes res.on('finish', () => { const duration = Date.now() - start console.log(`${req.method} ${req.path} ${res.statusCode} — ${duration}ms`) })
next()}
app.use(requestTimer)Every request now logs its method, path, status code, and duration.
The request lifecycle with middleware
Section titled “The request lifecycle with middleware”1. Request arrives2. requestTimer starts the clock, calls next()3. express.json() parses the body, calls next()4. cors() sets CORS headers, calls next()5. Route handler runs, sends response6. res 'finish' event fires → requestTimer logs durationThis is the pattern used throughout the Bulletin API: reusable, composable middleware functions that each do one thing.
Exercise
Section titled “Exercise”- Create
src/middleware/logger.tsthat logs[METHOD] path — timestampfor every request. - Register it in
src/index.tsbefore your routes. - Make several requests and verify the log output.
- What happens if you forget to call
next()in your middleware?
- Middleware functions have the signature
(req, res, next). - Call
next()to pass to the next middleware; callnext(error)to jump to error handling. - Register with
app.use(fn)— runs for all routes in the order registered. - Middleware builds a pipeline: logging → parsing → auth → route handler.
- If you don’t call
next()or send a response, the request hangs.