Hello readers π, welcome to the 14th blog in our Node.js series!
In the last few posts, we built a complete file upload and serving system using Multer and Express. We learned how to accept files, store them safely, and serve them back via static URLs. Today, we're going to shift focus to something more subtle but equally important: how to extract information from the URL itself. Specifically, we'll talk about URL parameters and query strings in Express.js.
You see these every day: URLs like /users/42 or /search?q=express&sort=asc. Knowing when to use a parameter (the 42) versus a query string (the ?q=express) makes your API design cleaner and more intuitive. Let's break it all down with clear examples.
Let's get started.
What URL parameters are
URL parameters (also called route parameters) are segments of the URL path that act as placeholders. They are part of the path itself and are typically used to identify a specific resource. In Express, you define them with a colon (:) prefix in the route path, and Express captures their values into req.params.
Think of <name> as a placeholder for something like an ID, a username, or a category. The actual value appears in the URL after the previous segment.
Here's a basic route that uses a parameter:
const express = require('express');
const app = express();
app.get('/users/:userId', (req, res) => {
const userId = req.params.userId;
res.send(`User profile for ID: ${userId}`);
});
If you visit http://localhost:3000/users/123, req.params.userId will be "123". Notice that the parameter is part of the path. It's not optional; the route wouldn't match /users/ alone (without an ID).
You can have multiple parameters in a single route:
app.get('/posts/:postId/comments/:commentId', (req, res) => {
const { postId, commentId } = req.params;
res.json({ postId, commentId });
});
The values are always strings, so you may need to parse them into numbers if needed (parseInt). Express doesn't do any type conversion automatically.
What query strings (query parameters) are
Query strings are key-value pairs that come after a question mark (?) in a URL. They are used to send additional data that is often optional, like filters, sorting options, pagination, or search terms. They are not part of the route path itself. In Express, you access them via req.query.
For example, a search URL might look like this:
/search?q=express&page=2&limit=10
Here, q, page, and limit are query parameters. Express parses them and makes them available in req.query as an object.
Example route:
app.get('/search', (req, res) => {
const query = req.query.q;
const page = req.query.page || 1;
const limit = req.query.limit || 10;
res.json({ query, page, limit });
});
A request to /search?q=node&page=3 gives:
{ "query": "node", "page": "3", "limit": 10 }
Query strings are extremely flexible because they don't affect route matching directly; any number of them can be added to a URL without breaking the route pattern.
Differences between URL parameters and query strings
The core difference lies in their purpose and placement.
| Aspect | URL Parameters (Route params) | Query Strings |
|---|---|---|
| Appearance in URL | Part of the path: /users/42
|
After ? in the URL: /users?role=admin
|
| Purpose | Identify a specific resource (required) | Provide additional instructions or filters (optional) |
| Route matching | Must be defined in route path with :param
|
Does not affect which route is matched |
| Express access |
req.params (object) |
req.query (object) |
| Typical use cases | Entity ID, username, category, blog post slug | Search, pagination, sorting, filtering, API keys |
| Order in URL | Fixed position in the path | Unordered, can be combined arbitrarily |
| Optionality | Usually required for the resource to make sense | Often optional with defaults |
A good mental model: parameters point to a specific "noun" (the resource); query strings describe "how" or "what to do" with that resource, like filtering or sorting.
Accessing params in Express
As shown earlier, you define placeholders in the route string using :paramName. Then inside the handler, you read req.params. All values are strings.
If you need to make a parameter optional and still match the base route, you can use a ? after the parameter name (like :userId?), but then you'd need to handle the case where it's undefined. Alternatively, define two routes: one with and one without the parameter.
Here's an example of an optional parameter:
app.get('/articles/:year?/:month?', (req, res) => {
const { year, month } = req.params;
res.send(`Articles ${year ? 'for ' + year : ''} ${month ? '/' + month : ''}`);
});
But be careful, req.params will contain strings for whatever segments exist.
Accessing query strings in Express
Query strings don't need to be declared. Any key=value pair after the ? will appear in req.query. If a key appears multiple times (e.g., ?tag=js&tag=node), Express by default gives you an array (only if you use a certain parsing library; the built-in qs library parses arrays with repeated keys). So req.query.tag might be ['js', 'node'].
Example with arrays:
app.get('/items', (req, res) => {
const tags = req.query.tags;
// tags could be a string or an array if multiple
const tagsArray = Array.isArray(tags) ? tags : [tags].filter(Boolean);
res.json({ tags: tagsArray });
});
When to use params vs query: practical examples
User profile ID (use params)
You want to get a specific user by ID. The ID is the identifier of the resource, so it belongs in the path.
app.get('/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
});
This makes the URL clean: /users/5. Without the ID, the route doesn't make sense. So a parameter is the right choice.
Search filters (use query strings)
You want to filter a list of products by category, price range, and sort order. None of these are "the resource identifier". They are optional, descriptive modifiers. So they go in the query string.
app.get('/products', (req, res) => {
const { category, minPrice, sort } = req.query;
let results = [...products];
if (category) results = results.filter(p => p.category === category);
if (minPrice) results = results.filter(p => p.price >= Number(minPrice));
if (sort === 'asc') results.sort((a, b) => a.price - b.price);
else if (sort === 'desc') results.sort((a, b) => b.price - a.price);
res.json(results);
});
A request URL like /products?category=electronics&minPrice=100&sort=asc clearly communicates that we're viewing the products collection but with specific filters.
Mixed usage
Often, you'll combine both. For example, fetching comments for a specific post, with pagination:
GET /posts/:postId/comments?page=2&limit=10
Here, :postId identifies the post (required), while page and limit control how many comments are returned (optional with defaults).
Visualizing the URL structure
Every URL can be broken down into parts. Let's disassemble an example:
https://api.example.com/users/42?include=posts&limit=5
-
Protocol & domain:
https://api.example.com -
Path:
/users/42- Segment
users(static) - Segment
42(dynamic, captured by:userId)
- Segment
-
Query string:
?include=posts&limit=5include=postslimit=5
Express routes match against the path (ignoring the query string). So you'd define:
app.get('/users/:userId', (req, res) => { ... });
And then inside, req.params.userId gives "42", req.query.include gives "posts", and req.query.limit gives "5".
This separation allows the same route to respond differently based on what the client asks for, without defining countless different URL patterns.
Conclusion
Understanding URL parameters versus query strings is essential for designing clean, predictable REST APIs. URL parameters are used to identify a specific resource (like a user or a post) and appear as dynamic segments in the path. Query strings are used to modify the request, provide filters, pagination, or any optional data, and appear after the ? in the URL. Express makes both accessible with req.params and req.query.
Let's quickly recap:
-
URL parameters (
:param) are parts of the path used for required resource identifiers. Accessed viareq.params. -
Query strings (
?key=value) are optional keyβvalue pairs used for filtering, sorting, searching, and other modifiers. Accessed viareq.query. - Route matching ignores query strings, so they are flexible and don't affect which handler runs.
- Use parameters when the URL would be incomplete without that piece of data; use query strings for everything else that's optional or descriptive.
- In Express, both are available as simple JavaScript objects, making it easy to extract and use them.
Armed with this understanding, your routes will become more intuitive and your APIs more predictable. See you in the next post!
Hope you found this helpful! If you spot any mistakes or have suggestions, let me know. You can find me on LinkedIn and X, where I post more about web development.
Top comments (0)