Suggest an editImprove this articleRefine the answer for “Route parameters and query strings in Express?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Route parameters and query strings** are two ways Express reads data from a URL. Route params (`/users/:id`) go into `req.params`. Query strings (`/users?page=2`) go into `req.query`. Both values arrive as strings, so parse numbers with `parseInt()`. **Key point:** use route params to identify a resource, query strings for filters and pagination.Shown above the full answer for quick recall.Answer (EN)Image**Route parameters and query strings** are two different ways Express reads input from the URL. Route params live in the path itself (`/users/:id`), query strings live after `?` (`/users?page=2`). They end up in different objects and serve different purposes. ## Theory ### TL;DR - Route params are part of the path structure: `/users/:id` matches `/users/123` and puts `'123'` into `req.params.id` - Query strings come after `?` and never affect route matching: `/users?page=2` still hits the `/users` handler - Decision rule: param for "which resource," query string for "how to return it" - Both values arrive as strings. `req.query.page` is `'2'`, not `2` ### Quick example ```javascript const express = require('express'); const app = express(); // Route param: identifies a specific user app.get('/users/:id', (req, res) => { // GET /users/123 → req.params.id = '123' res.json({ userId: req.params.id }); }); // Query string: filters or paginates a collection app.get('/users', (req, res) => { // GET /users?page=2&limit=10 const page = parseInt(req.query.page, 10) || 1; const limit = parseInt(req.query.limit, 10) || 20; res.json({ page, limit }); }); app.listen(3000); ``` Route params go into `req.params`. Query values go into `req.query`. They never share the same object. ### Key difference Express uses the `path-to-regexp` library to compile route strings into regex patterns at registration time. The `:id` segment becomes a named capture group, so every incoming request gets matched against it. Query strings are never part of this matching step. They sit after `?` in the URL and Node.js parses them separately into `req.query`. A request to `/users/123?sort=asc` matches `/users/:id`, gives you `req.params.id === '123'`, and also gives you `req.query.sort === 'asc'`, all at once. ### When to use - Single resource by ID: `/users/:id`, `/posts/:postId` - Nested resources: `/api/users/:userId/orders/:orderId` - Filtering a collection: `/users?status=active&role=admin` - Pagination and sorting: `/posts?page=2&sort=desc&limit=20` - Search input: `/search?q=nodejs` A simple test: if you remove the value and the URL no longer points to anything meaningful, put it in a route param. If removing it just changes what you get back, use a query string. ### How Express handles this internally At startup, `path-to-regexp` (v6+ in Express 4.18+) compiles `/users/:id` into a regex roughly equivalent to `/users/([^/]+?)`. On each incoming request, `router.match()` runs that regex and fills `req.params`. Query strings skip that logic entirely. Node's built-in `querystring` module reads everything after `?` in `req.url` and decodes it into the plain object you access as `req.query`. ### Common mistakes **Mistake: reading a route param from `req.query`** ```javascript // Route: app.get('/users/:id', ...) // GET /users/123 const id = req.query.id; // undefined, wrong object const { id } = req.params; // '123', correct ``` This trips up beginners more than anything else. In code reviews I see it on almost every junior PR that touches routing. `req.params` and `req.query` are two separate objects and they never overlap. **Mistake: not converting query string numbers** ```javascript // GET /users?page=2 const page = req.query.page; // '2', a string const offset = (page - 1) * 10; // works by accident via coercion // Better const page = parseInt(req.query.page, 10) || 1; // 2, a number ``` The coercion might look like it works, but `page === 2` returns `false` and `page > 1` returns `true`. Relying on implicit coercion causes subtle bugs. **Mistake: putting search queries in the path** ```javascript // Awkward: spaces and special characters cause encoding problems app.get('/search/:q', (req, res) => { ... }); // Better: query strings handle URL encoding naturally app.get('/search', (req, res) => { const q = req.query.q; // 'foo bar' already decoded by Express }); ``` **Mistake: ignoring repeated query keys** ```javascript // GET /posts?tag=js&tag=node // Express 4.18+: req.query.tag = ['js', 'node'] // Single value: req.query.tag = 'js' (a string) // Safe normalization const tags = [].concat(req.query.tag || []); ``` Express returns a string when a key appears once and an array when it appears more than once. Always normalize if your route accepts repeated keys. ### Real-world usage - GitHub: `/repos/:owner/:repo` for identity, `/repos?sort=stars&type=public` for filtering - Stripe: `/v1/customers/:id` for a specific customer, `?limit=10&starting_after=ch_123` for pagination - Twitter: `/2/tweets/:id` for a tweet, `?max_results=100&query=nodejs` for search - Any REST API follows the same convention: path params for resource identity, query strings for options ### Follow-up questions **Q:** What is the difference between `req.params`, `req.query`, and `req.body`? **A:** `req.params` is populated from path segments like `:id`. `req.query` is populated from the query string after `?`. `req.body` holds the request payload and only works after you add `express.json()` or `express.urlencoded()` middleware. **Q:** What happens when a route param and a query string share the same name, like `/users/:id?id=999`? **A:** They stay independent. `req.params.id` holds the path value and `req.query.id` holds `'999'`. Express never merges them. **Q:** How does Express decode percent-encoded characters like `/users/Joe%20Doe`? **A:** `path-to-regexp` decodes them automatically. `req.params.name` will be `'Joe Doe'`, not `'Joe%20Doe'`. **Q:** Can you make a route param optional? **A:** `:id?` works in Express 4.x, but two separate routes (`/users/:id` and `/users`) are usually cleaner. Optional params can create unexpected overlaps when other routes are nearby. **Q:** Why does the same query key sometimes give a string and sometimes an array? **A:** Because Express returns a string when the key appears once and an array when it appears more than once. Use `[].concat(req.query.tag)` to always get an array regardless of how many values are present. ## Examples ### Basic: user profile lookup ```javascript // GET /api/users/42 app.get('/api/users/:id', (req, res) => { const userId = parseInt(req.params.id, 10); // Look up user 42 in the database res.json({ userId }); }); ``` `req.params.id` is always a string. Parse it to a number before using it in a database query. ### Real-world: paginated repository list (GitHub-style) ```javascript // GET /api/users/octocat/repos?page=2&per_page=30&sort=updated app.get('/api/users/:username/repos', (req, res) => { const { username } = req.params; const page = parseInt(req.query.page, 10) || 1; const perPage = parseInt(req.query.per_page, 10) || 30; const sort = req.query.sort || 'updated'; // username = who, page/perPage/sort = how res.json({ username, page, perPage, sort }); }); ``` `username` identifies the resource. `page`, `per_page`, and `sort` control how it is returned. Swapping them (putting `page` in the path, or `username` in the query) breaks REST conventions and makes the API awkward to consume.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.