`/users/:id` and Friends — Capturing Path Segments
Route Parameters
Path parameters let URLs carry data. They land on req.params.
What you'll learn
- Use single and multiple params
- Make params optional
- Validate or coerce them
A route can capture parts of the URL with :name syntax. Captures
land on req.params.
Single Parameter
app.get("/users/:id", (req, res) => {
res.json({ id: req.params.id });
});
// GET /users/42 → { "id": "42" } Note: params are always strings. Coerce if you need a number:
const id = Number(req.params.id);
if (!Number.isInteger(id)) {
return res.status(400).json({ error: "id must be an integer" });
} Multiple Parameters
app.get("/posts/:postId/comments/:commentId", (req, res) => {
res.json(req.params);
// { postId: '42', commentId: '7' }
}); Optional Parameters (Express 5)
In Express 5, :name? makes a parameter optional:
app.get("/posts/:year/:month?", (req, res) => {
res.json(req.params);
// GET /posts/2026/05 → { year: '2026', month: '05' }
// GET /posts/2026 → { year: '2026' }
}); Wildcards / Splats
*splat captures the rest of the URL into req.params.splat:
app.get("/files/*splat", (req, res) => {
res.json({ path: req.params.splat });
// GET /files/2026/photos/01.jpg
// → { path: ['2026', 'photos', '01.jpg'] }
}); (Express 4 uses a bare * — different syntax.)
Constraining With Regex
Restrict what a parameter can match:
app.get("/users/:id(\\d+)", (req, res) => {
// only matches when :id is digits
res.json({ id: Number(req.params.id) });
}); Anything else falls through to the next route or 404.
app.param() — Run Code Per Param
app.param("id", (req, res, next, value) => {
// runs whenever a route has :id
req.userId = Number(value);
if (!Number.isInteger(req.userId)) {
return res.status(400).json({ error: "bad id" });
}
next();
});
app.get("/users/:id", (req, res) => {
// req.userId is set, validated
}); DRYs up validation across every route that uses :id.