`?page=1&limit=20` — Parameters on `req.query`
Query Strings
Query parameters live on req.query. Always strings; coerce or validate before use.
What you'll learn
- Read query params
- Coerce types safely
- Handle arrays and nested params
Query strings are the ?key=value parts of a URL. Express parses
them into req.query.
Basic
app.get("/search", (req, res) => {
const q = req.query.q;
const limit = req.query.limit ?? "10";
res.json({ q, limit });
});
// GET /search?q=express&limit=20
// → { "q": "express", "limit": "20" } Values are always strings, even numeric-looking ones. Coerce yourself:
const limit = Number(req.query.limit ?? 10); Arrays
Repeating a key gives an array:
// GET /posts?tag=js&tag=react
// req.query.tag → ['js', 'react'] A single value stays a string. Normalize to always-array:
const tags = [].concat(req.query.tag ?? []); Nested / Complex
By default Express uses qs for parsing, which supports nested:
// GET /search?filter[city]=NYC&filter[age]=30
// req.query.filter → { city: 'NYC', age: '30' } Disable nested parsing if you only want simple key/value:
app.set("query parser", "simple"); Validation With Zod
The robust pattern — never trust query types:
import { z } from "zod";
const PaginationSchema = z.object({
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
});
app.get("/posts", (req, res) => {
const { page, limit } = PaginationSchema.parse(req.query);
// page, limit are guaranteed numbers within bounds
res.json({ page, limit });
}); coerce turns "20" into 20, applies min/max, defaults
missing values. Three lines that prevent a class of bugs.
URL Length Limits
Browsers and servers cap URL length around 2-8KB. For lots of data, use POST with a body — not a giant query string.
The Response Object →