Middleware¶
Middleware runs in order. Put your auth check after your data handler and wonder why everyone sees everything
Every request walks through a chain of functions before it reaches your route handler. Each one can modify the request , end the response , or pass to the next. Order defines behavior. One misplaced middleware and your entire security model collapses
what middleware is¶
A function that receives req , res , and next and either:
- Modifies
reqorresand callsnext()to continue the chain - Ends the response with
res.send(),res.json(),res.end()etc - Throws or passes an error to
next(err)which skips to error handlers
function myMiddleware(req , res , next) {
// do something
console.log(`${req.method} ${req.path}`)
next() // pass control to next middleware
}
app.use() - global middleware¶
Middleware applied with app.use() runs on every request to every route
app.use(helmet()) // security headers - every route
app.use(cors()) // CORS - every route
app.use(express.json()) // parse JSON bodies - every route
app.use(morgan('dev')) // logging - every route
Order in app.use() is the order middleware executes
built-in middleware¶
Express ships with several middleware functions built in (no extra install needed):
// JSON body parser - replaces deprecated body-parser
app.use(express.json())
// Options: limit , inflate , reviver , strict , type , verify
// URL-encoded body parser
app.use(express.urlencoded({ extended: true }))
// extended: true uses qs library (nested objects)
// extended: false uses query-string library (flat)
// Static file serving
app.use(express.static('public'))
// Cookie parsing - no longer built-in in Express 4.x+
// Need: npm install cookie-parser
third-party middleware¶
The Express ecosystem is drowning in middleware packages. Here are the ones you actually need:
npm install helmet cors morgan express-rate-limit cookie-parser
const helmet = require('helmet')
const cors = require('cors')
const morgan = require('morgan')
const rateLimit = require('express-rate-limit')
app.use(helmet()) // security headers
app.use(cors({ origin: 'https://yourapp.com' })) // CORS policy
app.use(morgan('combined')) // HTTP request logging
app.use(rateLimit({ windowMs: 15 * 60 * 1000 , max: 100 })) // rate limiting
custom middleware¶
Writing your own middleware is trivial - and dangerous if you don't understand the chain
// request timer
app.use((req , res , next) => {
req.startTime = Date.now()
res.on('finish' , () => {
const duration = Date.now() - req.startTime
console.log(`${req.method} ${req.path} took ${duration}ms`)
})
next()
})
// auth check
const requireAuth = (req , res , next) => {
if (!req.headers.authorization) {
return res.status(401).json({ error: 'No auth token' })
}
next()
}
// apply to specific routes
app.get('/protected' , requireAuth , handler)
middleware execution order¶
This is where people fuck up. Hard
app.use(express.json()) // 1 - body parser
app.use(cors()) // 2 - CORS headers
app.get('/data' , handler) // 3 - route
app.use((err , req , res , next) => { // 4 - error handler
res.status(500).json({ error: 'Oops' })
})
What happens:
- Request hits Express
express.json()runs - parses body if JSONcors()runs - sets headers , handles preflight- Routes checked -
/datamatches , handler runs - If handler throws , error handler catches it
Now the broken version:
app.use((err , req , res , next) => { // ERROR HANDLER FIRST
res.status(500).json({ error: 'Oops' })
})
app.use(express.json()) // BODY PARSER SECOND
app.use(cors()) // CORS THIRD
app.get('/data' , handler) // ROUTE LAST
Error handler is just a middleware with 4 parameters. If you define it first it runs first - before the route , before body parsing , before everything. It'll catch nothing because nothing has happened yet
middleware execution order rules¶
- Error handlers must be last - after all routes and middleware
- Security middleware first - Helmet , CORS before anything
- Body parsers before routes - routes need parsed bodies
- Auth middleware before protected routes - obviously
multiple middleware on a route¶
const validate = (req , res , next) => {
if (!req.body.name) return res.status(400).json({ error: 'Name required' })
next()
}
const sanitize = (req , res , next) => {
req.body.name = req.body.name.trim()
next()
}
const log = (req , res , next) => {
console.log('Creating user:', req.body.name)
next()
}
router.post('/users' , validate , sanitize , log , createUserHandler)
This runs validate -> sanitize -> log -> createUserHandler in that order
common middleware pitfalls¶
// PITFALL 1: Forgetting to call next()
app.use((req , res , next) => {
console.log('I run but nothing happens after')
// forgot next() - request hangs until timeout
})
// PITFALL 2: Mixing up req and res parameter order
// (req , res , next) NOT (req , next , res)
// You'd be surprised how common this is
// PITFALL 3: Async errors not caught
app.use(async (req , res , next) => {
const data = await db.query('SELECT ...') // if this throws
// Express doesn't catch promise rejections
// Use express-async-errors or wrap manually
next()
})
prerequisites¶
express_03_routing.md - route params , query strings , express.Router()
next → express_05_static.md