Warming up the neural circuits...
By the end of this chapter you will:
Routing is matching a URL + method to a function. Do it cleanly and the rest of your is easy. Do it messily and every new endpoint becomes a fight.
Mail arrives at a sorting room. Each envelope has a destination — a street name and a house number. A clerk reads each address and drops it in the right bin: Domestic, International, Express, Bulk. Once the envelope is in its bin, it's someone else's problem.
That's routing. The URL is the address. The HTTP method is the kind of mail. The bin is the handler function. Your job is to sort, not to deliver.
Install Express:
npm install expressWrite a server:
const express = require('express');
const app = express();
app.use(express.json()); // parse JSON bodies
app.get('/', (req, res) => res.send('Hello'));
app.get('/users', (req, res) => res.json([{ id: 1, name: 'Aditi' }]));
app.post('/users', (req, res) => res.status(201).json({ ...req.body, id: 2 }));
app.listen(3000, () => console.log('http://localhost:3000'));Compare to the vanilla HTTP server from chapter 3 — this is half the code and does more.
Five things to know:
app.get('/about', handler);
app.post('/login', handler);Exact match.
app.get('/users/:id', (req, res) => {
res.json({ userId: req.params.id });
});:id is captured into req.params. You can have several: /users/:userId/posts/:postId.
Tip: route params are always strings. Cast to number / UUID-validate if needed.
// GET /search?q=node&limit=10
app.get('/search', (req, res) => {
const { q, limit } = req.query;
res.json({ q,
Anything after ? is parsed into req.query. Strings by default.
app.get('/files/*', handler); // matches /files/anything/hereUseful for proxies, static file servers. Don't use in regular routing.
app.use((req, res) => res.status(404).json({ error: 'Not found' }));Goes last. Catches anything that didn't match.
We covered the semantics in L0 chapter 3. Quick recap as it applies to Express:
app.get('/users', listUsers); // read all
app.get('/users/:id', getUser); // read one
app.post('/users', createUser)
Common newbie mistake: using POST for everything. Don't. The method conveys intent, drives caching behavior, and matters for security (CSRF defenses key off it).
Three places data comes from. Get them mixed up and you'll spend hours.
| Source | Comes from | Type | Example |
|---|---|---|---|
req.params | URL path | strings | /users/42 → { id: '42' } |
req.query | URL ? | strings | /search?q=x → { q: 'x' } |
req.body | Request body | parsed (JSON/) | { "name": "Aditi" } |
// POST /users/42/posts?notify=true
// Body: {"title": "Hello"}
app.post('/users/:id/posts', (req, res) => {
console.log(req.params.id); //
That 'true' is a string trap. Always cast/validate.
Putting every route in app.js works for 10 endpoints. After 50, it's a 2000-line file.
Use routers to split by resource:
const router = require('express').Router();
router.get('/', listUsers);
router.get('/:id', getUser);
router.post
const usersRouter = require('./routes/users');
const postsRouter = require('./routes/posts');
app.use('/api/users', usersRouter);
app.use('/api/posts'GET /api/users/42 now routes to getUser cleanly.
You can nest routers. Don't go more than 2 levels deep — at 3+, your URL design is probably wrong.
Express tries routes in the order they're defined. First match wins.
// Wrong order:
app.get('/users/:id', getUser);
app.get('/users/me', getMe); // never runs!Why? '/users/me' matches /users/:id first (with id = 'me'). The me route is unreachable.
Fix: put specific routes before generic ones.
// Right order:
app.get('/users/me', getMe);
app.get('/users/:id', getUser);1. Trailing slashes. /users and /users/ are different URLs. Express by default treats them as the same in routing but the difference matters in Location headers, redirects, and caching. Pick one (no trailing slash) and enforce it.
2. Encoding. /search?q=hello%20world arrives as req.query.q === 'hello world' — Express decodes for you. Don't decode again.
3. Case sensitivity. Express is case-sensitive on routes. /Users ≠ /users. Normalize at the edge (nginx lowercase) if your users won't.
You can chain handlers on one route:
app.get('/users/:id', requireAuth, loadUser, sendUser);Each one runs in order. Each one can next() to the next, or send a response. We'll spend all of next chapter on this.
| Mistake | Why it's wrong | What to do |
|---|---|---|
app.post('/getUser') | POST for a read; non-cacheable; CSRF risk | GET for reads |
/users/me defined after /users/:id | Never reached | Specific routes first |
Trusting req.params.id as a number | It's a string | Validate / cast |
Calling res.send twice | "Cannot set headers after they are sent" | Only one response per request |
| Putting business logic in routes | Untestable, repetitive | Move to a service layer |
Production. Define a single 404 handler at the end. Don't let unknown paths get away with default errors.
Performance. Express routing is already fast for hundreds of routes. Don't micro-optimize. If you need extreme speed, look at Fastify.
Security. Validate req.params.id shape (UUID? integer?) before using it in a DB query. Path traversal (/users/../../admin) is real if you naively concat paths.
/api/items — in-memory array, no DB./health endpoint.items, users). Mount them under /api.?limit=&cursor= to GET /api/items.Beginner. What's the difference between req.params and req.query? params come from path; query comes from URL search string. Both are strings.
Senior. Walk me through your folder structure for routes. Per-resource files, mounted under a versioned prefix (/api/v1), business logic in services not in routes.
Routing matches URL + method to a handler. Express gives you app.METHOD(path, handler). Use route params for identifiers, query strings for filters, body for payloads. Split routes into routers per resource. Define specific routes before generic ones. The handler chain ( + final handler) is the heart of Express — that's the next chapter.
app.get('/users/:id', handler) — params, query, body are three different sources./me before /:id.req.params and req.query?app.get('/users/:id', getUser); app.get('/users/me', getMe); in this order?DELETE /users/42 use? (Trick question.)app.use('/api', router) do?