DEV Community

Cover image for URL Parameters vs Query Strings in Express.js
SATYA SOOTAR
SATYA SOOTAR

Posted on

URL Parameters vs Query Strings in Express.js

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}`);
});
Enter fullscreen mode Exit fullscreen mode

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 });
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 });
});
Enter fullscreen mode Exit fullscreen mode

A request to /search?q=node&page=3 gives:

{ "query": "node", "page": "3", "limit": 10 }
Enter fullscreen mode Exit fullscreen mode

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 : ''}`);
});
Enter fullscreen mode Exit fullscreen mode

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 });
});
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • Protocol & domain: https://api.example.com
  • Path: /users/42
    • Segment users (static)
    • Segment 42 (dynamic, captured by :userId)
  • Query string: ?include=posts&limit=5
    • include=posts
    • limit=5

Express routes match against the path (ignoring the query string). So you'd define:

app.get('/users/:userId', (req, res) => { ... });
Enter fullscreen mode Exit fullscreen mode

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 via req.params.
  • Query strings (?key=value) are optional key‑value pairs used for filtering, sorting, searching, and other modifiers. Accessed via req.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)