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.
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
- Request in
- First match runs
- Responds or next()
- No reply: 404
2. Minimal App
express()makes the app;listen(port, cb)starts the serverres.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
- 1Body parsers
- 2Global middleware
- 3Routes
- 4404 handler
- 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
Routes, Params and Query Strings
1. Route Shape
app.METHOD(path, ...handlers): one verb, one pathapp.all()matches every methodapp.use()matches by path prefixapp.route('/books').get(list).post(create)chains one path
2. Params and Query
| Source | URL | Value |
|---|---|---|
| Param | /users/:id | req.params.id: '7' |
| Query | /search?q=js&page=2 | req.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.paramruns only for routes on that same router- Use
app.paramfor routes registered onapp
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.
Request and Response Objects
1. Reading the request
| Field | Holds |
|---|---|
req.params | path values, strings |
req.query | URL query: strings or arrays |
req.body | needs a parser |
req.headers | lowercase keys |
req.get('Header') | one header |
req.method req.path req.originalUrl req.ip | verb, path, full URL, client IP |
2. Sending the response
| Call | Sends |
|---|---|
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 204 | no body |
res.redirect(302, '/login')res.set('X-Id', '1')sets a headerres.cookie('sid', v, { httpOnly: true })res.sendFile(absPath)takes an absolute path
3. Per-request data and ending
res.localsandreq.usercarry data between middleware; auth setsreq.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.
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
- json()parse body
- authnext() or 401
- routeres.json
- error handlerlast
app.use(fn): every requestapp.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 4 | Express 5 | |
|---|---|---|
| Async throw | not caught | forwarded |
| Fix | try/catch + next(err) or wrapper | none 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 sendexpress.json()after routes:req.bodyis undefined- Error handler not the last layer
Built-in, Third-Party Middleware and Routers
1. Built-in and Third-Party
| Middleware | Does |
|---|---|
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-parser | fills req.cookies |
- Body size limit defaults to 100kb; set it with
{ limit } - Third-party:
npm i, thenapp.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.bodyis 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.
Express Cheat Sheet
1. Setup Order
- express()
- json, cors, helmet
- routers
- 404 handler
- error handler
- listen
2. Handlers and Status Codes
req.params.idpath,req.query.qURL,req.body.xneeds a parser- Reply with
res.status(n).json(obj) - Every middleware calls next() or responds
- Params are strings; query values are strings or arrays
| Code | Meaning |
|---|---|
200 | OK |
201 | Created |
204 | No Content |
400 | Bad input |
401 | Unauthenticated |
403 | Forbidden |
404 | Not found |
500 | Server 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
returnafter 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.
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, withreq.params.id === 'me' - First match wins, so put
/users/mefirst
app.post('/items', h) reads req.body.name, but app.use(express.json()) comes after it. What happens?
req.bodyis still undefined, so reading.namethrows 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);