Difficulty: Beginner
How does routing work in Express? Explain route parameters, query strings, and route patterns.
Express routing maps HTTP methods and URL patterns to handler functions. Routes are matched in the order they are defined, and the first match wins. Express supports static paths, parameterized paths with :param syntax, and regex patterns.
Route parameters (req.params) capture dynamic segments from the URL path. For example, /users/:id captures the id value. Query strings (req.query) capture key-value pairs after the ? in the URL. They are automatically parsed by Express.
Route patterns support wildcards, optional parameters with ?, and even regular expressions for complex matching. The router.param() method lets you add middleware that runs when a specific parameter is present.
Proper route ordering is crucial. More specific routes should come before general ones. A catch-all route should always be last, and 404 handlers should be defined after all other routes.
const express = require('express');
const app = express();
// Static route
app.get('/api/users', (req, res) => {
// Query strings: /api/users?role=admin&page=2
const { role, page = 1, limit = 10 } = req.query;
res.json({ role, page: Number(page), limit: Number(limit) });
});
// Route parameter
app.get('/api/users/:id', (req, res) => {
// /api/users/42 -> req.params.id = '42'
const { id } = req.params;
res.json({ userId: Number(id) });
});
// Multiple parameters
app.get('/api/users/:userId/posts/:postId', (req, res) => {
const { userId, postId } = req.params;
res.json({ userId, postId });
});
// Optional parameter (Express 5)
app.get('/api/archive/:year/:month?', (req, res) => {
const { year, month } = req.params;
res.json({ year, month: month || 'all' });
});
Parameters are always strings - cast them when needed. Query params come after ? and are parsed automatically. Route params are part of the URL path.
const express = require('express');
const app = express();
// Order matters! Specific routes before general ones
// 1. Exact match first
app.get('/api/users/me', (req, res) => {
res.json({ user: 'current user profile' });
});
// 2. Then parameterized route
app.get('/api/users/:id', (req, res) => {
res.json({ user: req.params.id });
});
// 3. Grouped by method
app.route('/api/posts')
.get((req, res) => res.json({ posts: [] }))
.post((req, res) => res.status(201).json(req.body))
.put((req, res) => res.status(405).json({ error: 'Method not allowed' }));
// 4. Catch-all 404 (must be LAST)
app.use('*', (req, res) => {
res.status(404).json({
error: 'Route not found',
path: req.originalUrl,
method: req.method
});
});
If /api/users/:id came before /api/users/me, the word 'me' would be captured as an id. Specific routes must come first.
const express = require('express');
const router = express.Router();
// Runs whenever :userId appears in any route
router.param('userId', async (req, res, next, id) => {
try {
const user = await db.users.findById(Number(id));
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
req.user = user; // Attach to request
next();
} catch (err) {
next(err);
}
});
// These routes all get req.user populated automatically
router.get('/:userId', (req, res) => {
res.json(req.user);
});
router.get('/:userId/posts', (req, res) => {
res.json({ user: req.user.name, posts: req.user.posts });
});
router.delete('/:userId', (req, res) => {
// req.user is already loaded
res.json({ deleted: req.user.id });
});
router.param() is middleware that triggers on a specific parameter. It avoids repeating the user lookup in every route handler.
Route Parameters, Query Strings, Route Patterns, req.params