Express Routing Patterns
Modular routing with express.Router, path parameters, query strings, nested routes, and async handler patterns.
1. express.Router — Modular Routes
// routes/users.js
const { Router } = require('express');
const router = Router();
router.get('/', async (req, res) => {
const users = await User.findAll();
res.json(users);
});
router.post('/', async (req, res) => {
const user = await User.create(req.body);
res.status(201).json(user);
});
router.get('/:id', async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) return res.status(404).json({ error: 'Not found' });
res.json(user);
});
module.exports = router;
// app.js — mount the router
const usersRouter = require('./routes/users');
app.use('/api/users', usersRouter);
2. Route Parameters
// Named params — :id, :slug
app.get('/posts/:year/:month/:slug', (req, res) => {
const { year, month, slug } = req.params;
res.json({ year, month, slug });
});
// Optional params with ?
app.get('/files/:name.:ext?', (req, res) => {
res.json({ name: req.params.name, ext: req.params.ext || 'txt' });
});
// Regex constraint
app.get('/users/:id(\\d+)', (req, res) => {
// only matches numeric IDs
res.json({ id: parseInt(req.params.id) });
});
// router.param — middleware for param processing
router.param('id', async (req, res, next, id) => {
try {
req.targetUser = await User.findById(id);
if (!req.targetUser) return res.status(404).json({ error: 'Not found' });
next();
} catch (err) {
next(err);
}
});
3. Query Parameters & Request Data
// GET /search?q=express&page=2&limit=20&sort=date:desc
app.get('/search', (req, res) => {
const {
q = '',
page = 1,
limit = 10,
sort = 'createdAt:asc',
} = req.query;
const [sortField, sortOrder] = sort.split(':');
res.json({
query: q,
pagination: { page: +page, limit: +limit },
sort: { field: sortField, order: sortOrder },
});
});
// Array query params: ?tags=a&tags=b
app.get('/filter', (req, res) => {
const tags = [].concat(req.query.tags || []);
res.json({ tags });
});
4. Nested Routers
// routes/posts.js
const router = Router({ mergeParams: true }); // mergeParams lets child access parent params
router.get('/', async (req, res) => {
// req.params.userId available because of mergeParams
const posts = await Post.findByUser(req.params.userId);
res.json(posts);
});
router.post('/', async (req, res) => {
const post = await Post.create({ ...req.body, userId: req.params.userId });
res.status(201).json(post);
});
module.exports = router;
// routes/users.js — mount nested router
const postsRouter = require('./posts');
router.use('/:userId/posts', postsRouter);
// Final URL: /api/users/:userId/posts
5. Async Handlers & Route Chaining
// Wrap async routes to forward errors
const wrap = fn => (req, res, next) => fn(req, res, next).catch(next);
// Method chaining on the same path
router.route('/items/:id')
.get(wrap(async (req, res) => {
const item = await Item.findById(req.params.id);
res.json(item);
}))
.put(wrap(async (req, res) => {
const item = await Item.update(req.params.id, req.body);
res.json(item);
}))
.delete(wrap(async (req, res) => {
await Item.delete(req.params.id);
res.status(204).end();
}));
// Multiple handlers per route (pipeline)
router.post('/checkout',
validateCart, // middleware 1
requireAuth, // middleware 2
wrap(async (req, res) => {
const order = await Order.create(req.body);
res.status(201).json(order);
})
);
6. HTTP Methods Reference
| Method | Typical Use | Body | Idempotent |
|---|---|---|---|
| GET | Read resource | No | Yes |
| POST | Create resource | Yes | No |
| PUT | Replace resource | Yes | Yes |
| PATCH | Partial update | Yes | No |
| DELETE | Remove resource | No | Yes |
| HEAD | Check existence | No | Yes |
| OPTIONS | CORS preflight | No | Yes |