NodeJSRouter-Level Middleware

Router-Level Middleware

Router-level middleware works exactly like application-level middleware, but it's bound to an express.Router() instance instead of the app. This lets you scope cross-cutting logic — auth, validation, logging — to a specific group of routes rather than the whole application. Combined with router modules, it's how large apps keep concerns local and organized.

Middleware on a router

routes/users.js

JS
import { Router } from 'express'
const router = Router()

// Runs for EVERY route in this router only:
router.use((req, res, next) => {
  console.log('users router:', req.method, req.originalUrl)
  next()
})

router.get('/', listUsers)
router.get('/:id', getUser)

export default router
`router.use` scopes middleware to that router's routes
Middleware added with `router.use` runs for the router's routes, not the entire app. Mount the router with `app.use('/users', usersRouter)` and the middleware applies to `/users`, `/users/:id`, etc. — but nothing else. This keeps resource-specific logic (like "load the user") next to the routes that need it.
The killer use case: scoped auth

Protect an entire section of your API by putting auth middleware at the top of a router — one line guards every route below it:

routes/admin.js

JS
import { Router } from 'express'
const router = Router()

// Gate the whole router — runs before every admin route:
router.use((req, res, next) => {
  if (!req.user) return res.status(401).json({ error: 'Login required' })
  if (!req.user.isAdmin) return res.status(403).json({ error: 'Forbidden' })
  next()
})

router.get('/stats', getStats)         // all protected automatically
router.get('/users', getAllUsers)
router.delete('/users/:id', deleteUser)

export default router
Order within the router still matters — guard first
Router middleware obeys the same [top-to-bottom rule](/nodejs/middleware-intro). The auth `router.use(...)` must be registered **before** the routes it protects; place it after a route and that route runs unguarded. Put protective and parsing middleware at the very top of the router file.
Per-route middleware

You can also attach middleware to a single route by listing it before the handler — ideal when only some routes need it:

JS
function validateBody(req, res, next) {
  if (!req.body.title) return res.status(422).json({ error: 'title required' })
  next()
}

// Only this route runs validateBody:
router.post('/', validateBody, createPost)

// Multiple per-route middleware run left to right:
router.put('/:id', requireAuth, validateBody, updatePost)
Three scopes: app, router, route — pick the narrowest that fits
Express gives you a scope spectrum: `app.use` (everything), `router.use` (one router), and per-route middleware (one endpoint). Choose the **narrowest** scope that covers your need — global middleware that only one route uses wastes work and obscures intent; per-route middleware copy-pasted across twenty routes should be hoisted to `router.use`.
Mounting middleware-only routers

A router can be purely middleware — useful for applying a stack of concerns to a path prefix without defining routes in that file:

JS
import { Router } from 'express'
import rateLimit from 'express-rate-limit'

const apiGuards = Router()
apiGuards.use(rateLimit({ windowMs: 60_000, max: 100 }))
apiGuards.use(requireApiKey)

// Apply the whole guard stack to everything under /api:
app.use('/api', apiGuards)
app.use('/api/users', usersRouter)    // these now sit behind the guards
router.param for resource loading

A router-scoped router.param runs whenever a given parameter appears in that router — the clean place to load-and-attach a resource (covered in route parameters):

JS
router.param('id', async (req, res, next, id) => {
  const user = await db.users.find(Number(id))
  if (!user) return res.status(404).json({ error: 'User not found' })
  req.user = user
  next()
})

router.get('/:id', (req, res) => res.json(req.user))         // req.user ready
router.put('/:id', (req, res) => res.json(update(req.user, req.body)))
App vs router middleware

Application-level

Router-level

Bound to

app

A Router() instance

Scope

Whole app

That router's mount path

Registered with

app.use()

router.use()

Best for

Global concerns (logging, body parse)

Resource-specific concerns (auth, loading)

Next
The middleware that ships inside Express itself: [Built-in Middleware](/nodejs/built-in-middleware).