The URL Decoder Ring: URL Parameters vs Query Strings in Express.js
Stop guessing how to send data. Here is the definitive guide to extracting identifiers and filters from your routes.
Look at the URL of almost any major web application, and you will see a trail of breadcrumbs.
Imagine you are browsing an e-commerce API. You might see a request that looks like this: GET /products/8932/reviews?rating=5&sort=recent
As a backend developer, your job is to look at that string of text, rip it apart, and figure out exactly what the client wants. In that URL, the client is sending us two very different types of data: the 8932 and the rating=5&sort=recent.
In Express.js, we handle these using URL Parameters and Query Strings.
Both are ways to pass data from the client to the server via the URL. But they serve entirely different architectural purposes. Using them incorrectly makes your API confusing to consume and harder to maintain. Let's break down exactly what they are, how to access them in Express, and the golden rule for when to use which.
URL Parameters: The Identifiers
URL Parameters (often just called "params") are structural parts of the URL path. They are used to point to a specific, unique resource.
Think of a URL parameter like a house number in a physical address. If you change the house number, you are looking at a completely different building. In a REST API, params tell the server which exact thing you want to interact with.
When you define a route in Express, you create a placeholder for a parameter by prefixing a path segment with a colon (:).
Here is how you capture a specific User Profile ID:
const express = require('express');
const app = express();
// The ":id" tells Express to treat whatever value is in that slot as a parameter
app.get('/users/:id', (req, res) => {
// Express automatically parses the URL and puts the value in req.params
const userId = req.params.id;
// Simulate a database lookup
const user = db.findUserById(userId);
if (!user) {
return res.status(404).json({ error: "User not found" });
}
res.json(user);
});
If a client makes a request to GET /users/42, Express intercepts it, grabs the 42, and attaches it to req.params.id.
The defining characteristic of a URL parameter is that it is mandatory for that specific route. If the client just requests GET /users/, that request will not hit the /users/:id route at all. It will fall through or throw a 404 because the identifier is missing.
Query Strings: The Modifiers
Query Strings (or query parameters) are optional key-value pairs tacked onto the very end of a URL.
They always begin with a question mark (?), and multiple pairs are separated by an ampersand (&). For example: /users?role=admin&active=true.
If URL parameters are the house address, query strings are the instructions you give to the person knocking on the door: "Show me the houses, but only the ones with three bedrooms, and sort them by price."
They are used for filtering, sorting, searching, and pagination. They do not change what resource you are targeting; they change how you want to view that resource.
Because query strings are entirely optional, you don't define them in your Express route path. The route path stays clean. Express automatically parses anything after the ? and drops it into req.query.
// Notice the route is just '/users'. We don't mention the query here.
app.get('/users', (req, res) => {
// Express parses the key-value pairs into the req.query object
const roleFilter = req.query.role;
const sortBy = req.query.sort;
let users = db.getAllUsers();
// 1. Filter the data if the client provided a 'role' query
if (roleFilter) {
users = users.filter(user => user.role === roleFilter);
}
// 2. Sort the data if the client provided a 'sort' query
if (sortBy === 'asc') {
users.sort((a, b) => a.name.localeCompare(b.name));
}
res.json(users);
});
If a client requests GET /users, they get the whole list. If they request GET /users?role=admin&sort=asc, Express populates req.query as { role: 'admin', sort: 'asc' }, and your logic filters the list down before sending it back.
The Golden Rule: When to use which?
When you are designing an API, it can sometimes feel ambiguous whether a piece of data should be a param or a query.
"Should I use /users/:role to get admins, or /users?role=admin?"
Here is the rule of thumb to keep your API clean and predictable:
1. Use URL Parameters (req.params) for Identity. If the data dictates which specific record the server needs to fetch from the database, it belongs in the path. If dropping the value makes the request conceptually invalid, it’s a parameter. You cannot fetch "user number 42" without the "42".
2. Use Query Strings (req.query) for Modifiers. If the data is used to filter, sort, search, or paginate a collection of items, it belongs in a query string. If you remove the query string, the route should still work perfectly—it should just return a broader, default set of data.
Putting It All Together
The best APIs use both of these concepts in harmony.
Let's go back to our very first example: a client wanting to see the 5-star reviews for a specific product, sorted by the newest first.
GET /products/8932/reviews?rating=5&sort=recent
Here is how easily Express handles that exact route:
app.get('/products/:productId/reviews', (req, res) => {
// 1. Identify the core resource (Param)
const productId = req.params.productId;
// 2. Extract the modifiers (Query)
const ratingFilter = req.query.rating;
const sortOrder = req.query.sort;
// Execute business logic...
const reviews = db.getReviews(productId, ratingFilter, sortOrder);
res.json(reviews);
});
The Takeaway
Express makes extracting data from URLs practically effortless. req.params handles the structure, req.query handles the modifiers.
But the real challenge isn't the syntax—it's the architecture. When you design your routes, treat your URL parameters as the rigid skeleton of your API, and treat query strings as the flexible dials and switches your clients can use to customize their view. Keep them separated in their proper roles, and your API will intuitively make sense to whoever consumes it next.

