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

MethodTypical UseBodyIdempotent
GETRead resourceNoYes
POSTCreate resourceYesNo
PUTReplace resourceYesYes
PATCHPartial updateYesNo
DELETERemove resourceNoYes
HEADCheck existenceNoYes
OPTIONSCORS preflightNoYes