Handbooks / Node.js / Chapter 4

Express Routing and Middleware

7 pages · ~5 min✓ Reviewed

Builds on Streams and Buffers. Next up: Error Handling and Async Patterns.

Node.jsPage 1

Express Mental Model and App Setup

1. What Express Is

  • Express = thin Node HTTP layer; the app is a function (req, res)
  • Each request goes through an ordered stack of middleware and route handlers
  • First matching layer runs: it ends the response or calls next()
  • If nothing responds, Express returns 404
  1. Request in
  2. First match runs
  3. Responds or next()
  4. No reply: 404

2. Minimal App

  • express() makes the app; listen(port, cb) starts the server
  • res.send() ends the response
javascript
const app = express()
app.get('/', (req, res) => res.send('hi'))
app.listen(3000, () => console.log('up'))
output
up

3. Setup Order and Proxy

  1. 1Body parsers
  2. 2Global middleware
  3. 3Routes
  4. 4404 handler
  5. 5Error handleralways last
  • app.set('trust proxy', 1) behind a proxy or load balancer
  • Makes req.ip and req.protocol correct
  • app.disable('x-powered-by') hides the header
Node.jsPage 2

Routes, Params and Query Strings

1. Route Shape

  • app.METHOD(path, ...handlers): one verb, one path
  • app.all() matches every method
  • app.use() matches by path prefix
  • app.route('/books').get(list).post(create) chains one path

2. Params and Query

SourceURLValue
Param/users/:idreq.params.id: '7'
Query/search?q=js&page=2req.query: { q: 'js', page: '2' }
Repeated key?t=a&t=b['a', 'b']
  • Params are strings: use Number(req.params.id)
  • Query values are strings or arrays, never numbers
  • Optional: Express 4 :id?, Express 5 /users{/:id}
  • In Express 5, :id? throws at startup

3. Order and Param Hooks

  • Routes match in registration order; first match wins
  • router.param runs only for routes on that same router
  • Use app.param for routes registered on app
javascript
router.param('id', (req, res, next, id) => {
  req.userId = Number(id);
  next();
});
router.get('/users/:id', byId);

routes on the router, not on app

Specific first

Register /users/me before /users/:id, or 'me' is captured as an id.

Node.jsPage 3

Request and Response Objects

1. Reading the request

FieldHolds
req.paramspath values, strings
req.queryURL query: strings or arrays
req.bodyneeds a parser
req.headerslowercase keys
req.get('Header')one header
req.method req.path req.originalUrl req.ipverb, path, full URL, client IP

2. Sending the response

CallSends
res.send(x)string, buffer or object
res.json(obj)JSON, Content-Type set
res.status(201).json(obj)chains status, then body
res.sendStatus(404)body 'Not Found'
a 204no body
  • res.redirect(302, '/login')
  • res.set('X-Id', '1') sets a header
  • res.cookie('sid', v, { httpOnly: true })
  • res.sendFile(absPath) takes an absolute path

3. Per-request data and ending

  • res.locals and req.user carry data between middleware; auth sets req.user
  • Ends: send, json, end, redirect, sendFile, sendStatus
  • Does not end: set, status, cookie
js
if (!req.user) {
  return res.status(401).json({ error: 'login' });
}
res.json({ ok: true });

return after responding early

Second response

A second send throws Cannot set headers after they are sent. Always return after an early response.

Node.jsPage 4

Middleware Chain, next() and Error Handling

1. What Middleware Is

  • Signature: (req, res, next)
  • Can change req or res
  • Can end the response
  • Or call next() to pass control on

2. Order = Execution Order

  1. json()parse body
  2. authnext() or 401
  3. routeres.json
  4. error handlerlast
  • app.use(fn): every request
  • app.use('/api', fn): only that prefix
  • No response and no next() = request hangs
  • next(err) skips ahead to error middleware

3. Errors and Async

Express 4Express 5
Async thrownot caughtforwarded
Fixtry/catch + next(err) or wrappernone needed
javascript
app.get('/u', async (req, res, next) => {
  try { res.json(await find()) }
  catch (e) { next(e) }
})
app.use((err, req, res, next) => {
  res.status(500).json({ error: 'fail' })
})

four arguments = error handler, registered after all routes

4. Common Mistakes

  • Forgot next(): request hangs
  • next() after responding: second send
  • express.json() after routes: req.body is undefined
  • Error handler not the last layer
Node.jsPage 5

Built-in, Third-Party Middleware and Routers

1. Built-in and Third-Party

MiddlewareDoes
express.json()fills req.body (JSON)
express.urlencoded({ extended: true })fills req.body (forms)
express.static('public')serves files
cors()cross-origin access
helmet()security headers
morgan('dev')request logs
cookie-parserfills req.cookies
  • Body size limit defaults to 100kb; set it with { limit }
  • Third-party: npm i, then app.use()

2. Routers

javascript
const router = express.Router();
router.use(auth);
router.get('/:id', handler);
module.exports = router;
// app.js
app.use('/users', router);

GET /users/7 reaches router.get('/:id')

  • Router paths are relative to the mount point
  • router.use(auth) protects only that router
  • Inline middleware: app.get('/admin', auth, handler)

3. Mistakes

  • No body parser: req.body is undefined
  • Wide-open cors() in production
  • Route order: an earlier route shadows the router
  • Error handler leaking secrets or stack traces
Order

Parsers first, then routers, error handler last.

Node.jsPage 6

Express Cheat Sheet

1. Setup Order

  1. express()
  2. json, cors, helmet
  3. routers
  4. 404 handler
  5. error handler
  6. listen

2. Handlers and Status Codes

  • req.params.id path, req.query.q URL, req.body.x needs a parser
  • Reply with res.status(n).json(obj)
  • Every middleware calls next() or responds
  • Params are strings; query values are strings or arrays
CodeMeaning
200OK
201Created
204No Content
400Bad input
401Unauthenticated
403Forbidden
404Not found
500Server error
javascript
app.use('/api/users', usersRouter);
app.use((err, req, res, next) => {
  res.status(500).json({ error: 'Oops' });
});

mount a router; error handler takes 4 args

3. Final Checklist

  • Parser before routes
  • Specific routes before dynamic ones
  • return after responding
  • Error handler last
  • Async errors forwarded to next(err)
Interview point

Why four arguments? Express spots error handlers by arity. Why return after res.json? A second send throws.

Node.jsPage 7

Check yourself

You register app.get('/users/:id', a) and then app.get('/users/me', b). Which handler runs for GET /users/me?
  • Handler a, with req.params.id === 'me'
  • First match wins, so put /users/me first
app.post('/items', h) reads req.body.name, but app.use(express.json()) comes after it. What happens?
  • req.body is still undefined, so reading .name throws a TypeError
  • Register the body parser before the routes
What is wrong here?
  • With no user, it responds twice and throws 'Cannot set headers after they are sent'
  • Fix: return res.status(404).json(...)
if (!user) res.status(404).json({ error: 'none' });
res.json(user);