NodeJSQuery Parameters

Query Parameters

Query parameters are the ?key=value pairs after a URL's path — the place for optional, modifying input: filtering, sorting, pagination, search terms. Express parses them automatically into req.query, sparing you the manual URL parsing you did with raw Node. The convenience comes with the same caveats — everything is a string, and the parsed shape can surprise you.

Reading req.query

JS
// GET /search?q=node&page=2&sort=recent
app.get('/search', (req, res) => {
  const { q, page, sort } = req.query

  console.log(q)      // 'node'
  console.log(page)   // '2'        ← a STRING, not a number
  console.log(sort)   // 'recent'

  res.json({ q, page, sort })
})
`req.query` is parsed for you — no `new URL()` needed
In raw Node you built a `URL` and read `searchParams`; Express does this on every request and hands you a plain object in `req.query`. The keys are the parameter names, the values are the (decoded) values. This is one of Express's quietly biggest conveniences.
The string trap, again
Query values are strings — and `?flag=false` is TRUTHY
Every value in `req.query` is a string. `?page=2` gives `'2'`, and crucially `?active=false` gives the string `'false'` — which is **truthy** in JavaScript. Writing `if (req.query.active)` is `true` even for `?active=false`. Convert and compare explicitly: `req.query.active === 'true'` for booleans, `Number(...)` for numbers, with sane defaults.

JS
app.get('/products', (req, res) => {
  // Coerce + default + clamp — never trust the raw strings:
  const page  = Math.max(1, parseInt(req.query.page, 10) || 1)
  const limit = Math.min(100, Math.max(1, parseInt(req.query.limit, 10) || 20))
  const inStock = req.query.inStock === 'true'       // explicit boolean
  const sort  = ['name', 'price', 'recent'].includes(req.query.sort)
    ? req.query.sort : 'name'                         // allow-list the value

  res.json(queryProducts({ page, limit, inStock, sort }))
})
Allow-list enumerated values like `sort`
For parameters with a fixed set of valid values (sort fields, order directions), check against an **allow-list** and fall back to a default rather than passing the raw string downstream. This prevents both bugs (a typo'd sort field) and injection (a malicious value reaching a database query).
Arrays and objects in the query
`req.query` values aren't always strings — repeated keys become arrays
Express uses the `qs` library to parse queries, so the shape is richer (and trickier) than you might expect. Repeated keys produce **arrays**, and bracket notation produces **nested objects** — meaning a parameter you assume is a string can arrive as an array or object, and `.trim()` on it would throw. Always tolerate the unexpected type for inputs an attacker controls.

JS
// ?tag=js&tag=node
req.query.tag            // ['js', 'node']   ← array for repeated keys

// ?filter[status]=open&filter[role]=admin
req.query.filter         // { status: 'open', role: 'admin' }  ← nested object

// ?ids[]=1&ids[]=2
req.query.ids            // ['1', '2']

Defensive normalization

JS
function asArray(value) {
  if (value === undefined) return []
  return Array.isArray(value) ? value : [value]   // tolerate single vs many
}

app.get('/items', (req, res) => {
  const tags = asArray(req.query.tag)             // always an array
  res.json(filterByTags(tags))
})
Configuring the query parser

Express lets you choose how queries are parsed. The default 'extended' mode (via qs) supports the nested-object/array behavior above; 'simple' mode (querystring) is flatter:

JS
// Disable rich parsing for a flatter, more predictable req.query:
app.set('query parser', 'simple')

// Or a custom parser function:
app.set('query parser', (str) => new URLSearchParams(str))
`'simple'` mode avoids nested-object surprises
If you don't need bracket-nesting and want `req.query` values to be plain strings (or arrays for duplicates), set the query parser to `'simple'`. This removes a class of "why is my param an object?" bugs and reduces the attack surface of deeply-nested query injection. Choose richness only if your API genuinely uses it.
query vs params — a reminder

req.params

req.query

Source

Path segments (/users/:id)

After ?

Required?

Yes (part of the route)

Optional

Use for

Identifying a resource

Filter / sort / paginate

Example

/users/42

/users?sort=name&page=2

Next
A full tour of everything Express attaches to the incoming request: [The Request Object](/nodejs/express-request).