Almost every route reads an id from the path or filters from the query string.
Route parameters are named segments starting with a colon. Their values appear on req.params, always as strings:
app.get("/users/:userId/posts/:postId", (req, res) => {
const { userId, postId } = req.params; // "42", "7"
});Convert and validate them yourself: Number(req.params.userId) may be NaN. Values are URL-decoded for you.
Express 5 uses a new path syntax (path-to-regexp v8) that is stricter than Express 4's:
- Optional parts use braces:
/files/:name{.:ext}matches/files/reportand/files/report.pdf. The old?suffix is gone. - Wildcards must be named:
/assets/*filepathmatches everything after/assets/, andreq.params.filepathis an array of segments (["css", "site.css"]). Use/{*splat}to also match the root. - Regular-expression characters inside path strings (
(,[,+,?) are no longer supported; use an array of paths or a realRegExpobject instead. - Unmatched optional parameters are simply absent from
req.params.
The query string (?page=2&tag=node) is parsed into req.query. Express 5's default "simple" parser produces strings, or arrays of strings when a key repeats (?tag=a&tag=b). Nested syntax like filter[age]=3 is not expanded unless you opt in with app.set("query parser", "extended").
Treat req.query as read-only, untrusted input: in Express 5 it is a getter you can't reassign, every value might be missing, a string, or an array, and numbers need converting. Path parameters identify a resource; query parameters filter, sort and paginate it.
Example
import express from "express";
const app = express();
app.get("/users/:id", (req, res) => {
const id = Number(req.params.id); // params are strings
if (!Number.isInteger(id)) return res.status(400).json({ error: "id must be an integer" });
res.json({ id });
});
app.get("/files/:name{.:ext}", (req, res) => { // optional part (Express 5 syntax)
res.json(req.params); // /files/a -> { name: "a" }; /files/a.pdf -> { name: "a", ext: "pdf" }
});
app.get("/assets/*filepath", (req, res) => { // named wildcard
res.json({ segments: req.params.filepath }); // /assets/css/site.css -> ["css","site.css"]
});
app.get("/search", (req, res) => {
const q = typeof req.query.q === "string" ? req.query.q : "";
const page = Math.max(1, Number.parseInt(req.query.page ?? "1", 10) || 1);
res.json({ q, page, tags: [req.query.tag ?? []].flat() }); // ?tag=a&tag=b -> ["a","b"]
});
app.listen(3000);Remember
req.paramsvalues are decoded strings — convert and validate- Express 5: optional segments use
{}; wildcards are named (*name) and give arrays req.queryis read-only, and each value may be absent, a string or an array
Watch out for
- Using Express 4 patterns like
/:id?or/*, which throw at startup in Express 5. - Assuming
req.query.tagis a string when a repeated key makes it an array.