NodeJSApplication-Level Middleware

Application-Level Middleware

Application-level middleware is bound to the app instance with app.use() (or app.METHOD()) and runs for requests across the whole application. It's where you put cross-cutting concerns — logging, body parsing, authentication, security headers — that should apply broadly. This page covers the forms app.use takes and the ordering discipline that makes them work.

Mounting for every request

JS
const app = express()

// No path → runs for EVERY request, regardless of method or URL:
app.use((req, res, next) => {
  req.requestTime = Date.now()
  next()
})
`app.use` with no path matches all requests
Omit the path and the middleware runs for every incoming request (every method, every URL). This is the right place for things that are truly global: request logging, attaching a request id, parsing bodies, setting security headers. It always calls `next()` (unless it's deliberately short-circuiting) so the request continues.
Mounting on a path prefix

JS
// Runs only for requests whose path STARTS WITH /api:
app.use('/api', (req, res, next) => {
  res.set('X-API-Version', '2')
  next()
})

// /api, /api/users, /api/users/42  → runs
// /home, /about                    → skipped
A path in `app.use` is a PREFIX match, not an exact match
`app.use('/api', fn)` matches `/api` **and everything under it** (`/api/users`, `/api/users/42`). This differs from `app.get('/api', fn)`, which matches the path *exactly*. The prefix behavior is what makes `app.use('/api', router)` mount a whole sub-tree — but it surprises people expecting an exact match. Use `app.get/post/...` when you mean one specific path.
The canonical setup order

Real apps register application middleware in a deliberate sequence. Order is everything — here's a typical, correct arrangement:

Typical app.js setup

JS
import express from 'express'
import helmet from 'helmet'
import cors from 'cors'
import morgan from 'morgan'

const app = express()

// 1. Security headers — early, so every response gets them
app.use(helmet())

// 2. CORS — before routes that browsers call cross-origin
app.use(cors())

// 3. Request logging
app.use(morgan('dev'))

// 4. Body parsing — BEFORE any handler that reads req.body
app.use(express.json())
app.use(express.urlencoded({ extended: true }))

// 5. Your routes
app.use('/api/users', usersRouter)
app.use('/api/posts', postsRouter)

// 6. 404 handler — after all routes
app.use((req, res) => res.status(404).json({ error: 'Not Found' }))

// 7. Error handler — LAST, four parameters
app.use((err, req, res, next) => {
  console.error(err)
  res.status(err.status || 500).json({ error: 'Internal Server Error' })
})
Body parsers before routes; 404 and error handlers after
This ordering isn't stylistic — it's required. Body parsers must precede handlers that use `req.body`. Security/CORS middleware must precede the routes they protect. The 404 catch-all must come *after* all real routes (or it'd intercept them), and the error handler must be **last** so it can catch errors from everything above. Shuffle these and the app breaks in subtle ways.
Multiple handlers at once

JS
// app.use accepts several middleware; they run in order:
app.use(middlewareA, middlewareB, middlewareC)

// Equivalent to:
app.use(middlewareA)
app.use(middlewareB)
app.use(middlewareC)
Conditional and environment-specific middleware

JS
// Only add verbose logging outside production:
if (process.env.NODE_ENV !== 'production') {
  app.use(morgan('dev'))
}

// Guard a subtree behind auth:
app.use('/admin', requireAdmin)
Branch on `NODE_ENV` for dev-only middleware
Development tools (verbose loggers, error stack traces in responses) shouldn't run in production. Gate them behind a `NODE_ENV` check at registration time. Conversely, some middleware (rate limiting, strict security) you may want *only* in production. Deciding what to register based on environment is a normal and important part of app setup.
A practical request logger

JS
app.use((req, res, next) => {
  const start = Date.now()
  // 'finish' fires when the response is fully sent:
  res.on('finish', () => {
    const ms = Date.now() - start
    console.log(`${req.method} ${req.originalUrl} ${res.statusCode} - ${ms}ms`)
  })
  next()                              // continue immediately; log on finish
})
Log on the response's `finish` event to capture status and timing
To log the final status code and duration, don't log in the middleware body (the response hasn't been sent yet). Instead, record the start time, attach a `res.on('finish', ...)` listener, and call `next()`. When the response completes, `finish` fires with the real `res.statusCode` and elapsed time. (In practice, use `morgan` — but this shows the mechanism.)
Next
Scope middleware to a single router instead of the whole app: [Router-Level Middleware](/nodejs/router-middleware).