The query string is not part of the route path. /books/42?fields=title matches /books/:id exactly as /books/42 does, and the parsed query arrives separately on req.query. You never register a route for a query parameter; you read it inside the handler and branch there.
Order decides the rest. Express 24,430 tries layers in registration order and stops at the first that responds, so a literal path must be registered before the parameterized path that would also swallow it.
app.get('/books/featured', (req, res) => res.json({ route: '/books/featured' }));
app.get('/books/:id', (req, res) => res.json({ route: '/books/:id', params: req.params }));
app.get('/search', (req, res) => res.json(req.query));
app.use((req, res) => res.status(404).json({ error: 'Not Found', path: req.path }));200 /books/featured {"route":"/books/featured"}
200 /books/42?fields=title {"route":"/books/:id","params":{"id":"42"}}
404 /authors {"error":"Not Found","path":"/authors"}
200 /search?tag=js&tag=node&page=2 {"tag":["js","node"],"page":"2"}
200 /search?filter[year]=2026 {"filter[year]":"2026"}Swap the first two lines and /books/featured returns {"id":"featured"} forever: :id matches the literal word, responds, and the walk never reaches the route you meant. The 404 layer at the bottom works for the opposite reason — it is a path-less app.use, so it matches everything that survives the layers above it.
The last two results show the Express 5 query parser. Repeated keys still collapse into an array, but the bracket notation filter[year] stays a literal key, because the default parser changed from extended (the qs library, which builds nested objects) to simple (Node's querystring). Nesting was a recurring source of prototype-pollution and parameter-count denial-of-service reports, so shallow is the safer default. If a client depends on nesting, opt back in with app.set('query parser', 'extended') and the same request yields {"filter":{"year":"2026"}}. One more change: req.query is a lazy getter, not a writable property, so middleware that used to normalize input by assigning to it must write elsewhere, such as req.validated.