Skip to content

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.

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:

  1. Execute any code
  2. Modify req or res
  3. End the request by sending a response
  4. Call next() to pass to the next function in the pipeline
Request → [logger] → [cors] → [body-parser] → [auth] → route handler → Response

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.

Use app.use() to register middleware:

// Applies to ALL routes
app.use(myMiddleware)
// Applies only to routes starting with /api
app.use('/api', myMiddleware)
// Applies only to one specific route
app.get('/posts', myMiddleware, routeHandler)

Middleware registered with app.use() runs in the order it’s registered. Order matters.

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.

1. Request arrives
2. 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 response
6. res 'finish' event fires → requestTimer logs duration

This is the pattern used throughout the Bulletin API: reusable, composable middleware functions that each do one thing.

  1. Create src/middleware/logger.ts that logs [METHOD] path — timestamp for every request.
  2. Register it in src/index.ts before your routes.
  3. Make several requests and verify the log output.
  4. 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; call next(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.