Route parameters, wildcards and query strings

Express · Essentials · 12 min

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/report and /files/report.pdf. The old ? suffix is gone.
  • Wildcards must be named: /assets/*filepath matches everything after /assets/, and req.params.filepath is 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 real RegExp object 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.params values are decoded strings — convert and validate
  • Express 5: optional segments use {}; wildcards are named (*name) and give arrays
  • req.query is 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.tag is a string when a repeated key makes it an array.

Check yourself

  1. In Express 5, which path matches both /docs and /docs/guide/intro?
  2. A request to /users/abc hits app.get('/users/:id'). What is req.params.id?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: expressjs.com/en/guide/routing