URL Parameters vs Query Strings in Express.js
Both live in a URL. Both pass data to your server. But they do completely different jobs — and mixing them up is one of the most common Express.js mistakes.
What Is a URL Made Of?
Before we compare, let's look at a full URL and understand each part:
https://api.example.com/users/42/posts?sort=recent&limit=10
https:// → Protocol
api.example.com → Host
/users/ → Route path
42 → URL Parameter ← identifies WHO
/posts → Route path continues
? → Start of query string
sort=recent → Query param ← modifies HOW
&limit=10 → Another query param
📌 [Insert URL Anatomy Diagram here]
req.params.id → "42" req.query.sort → "recent" | req.query.limit → "10"
What Are URL Parameters?
URL parameters (also called route parameters) are dynamic segments inside the route path itself. They identify a specific resource.
Think of them as: "Which one?"
/users/42 → user with ID 42
/products/99 → product with ID 99
/posts/hello-world → post with slug "hello-world"
The :id syntax in Express marks a segment as a parameter:
// Route definition
app.get("/users/:id", (req, res) => {
console.log(req.params.id); // "42"
res.send(`Fetching user ${req.params.id}`);
});
When someone hits /users/42, Express matches the pattern and puts "42" into req.params.id.
Multiple URL Parameters
You can have more than one:
app.get("/users/:userId/posts/:postId", (req, res) => {
const { userId, postId } = req.params;
console.log(userId); // "42"
console.log(postId); // "7"
res.send(`User \({userId}, Post \){postId}`);
});
Hit /users/42/posts/7 and both params are populated.
What Are Query Strings?
Query strings come after the ? in a URL. They are optional key-value pairs that modify or filter the response — not identify a specific resource.
Think of them as: "How do you want it?"
/posts?sort=recent → sort by most recent
/products?category=shoes → filter by category
/users?search=aarav&page=2 → search + paginate
In Express, they're available on req.query:
app.get("/posts", (req, res) => {
console.log(req.query.sort); // "recent"
console.log(req.query.limit); // "10"
res.send(`Sort: \({req.query.sort}, Limit: \){req.query.limit}`);
});
Hit /posts?sort=recent&limit=10 and both values are available.
Multiple Query Params
app.get("/products", (req, res) => {
const { category, minPrice, maxPrice, sort, page } = req.query;
console.log(category); // "shoes"
console.log(minPrice); // "500"
console.log(maxPrice); // "3000"
console.log(sort); // "price_asc"
console.log(page); // "2"
});
URL: /products?category=shoes&minPrice=500&maxPrice=3000&sort=price_asc&page=2
All query params arrive as strings — even numbers. Always parse them when needed:
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
Side-by-Side Comparison
📌 [Insert Params vs Query Strings Comparison Diagram here]
| URL Parameters | Query Strings | |
|---|---|---|
| Syntax | /users/:id |
?sort=asc&limit=10 |
| Position | Inside the route path | After ? in the URL |
| Purpose | Identify a resource | Filter, sort, modify |
| Required? | Yes — route won't match without it | No — all optional |
| Access | req.params.id |
req.query.sort |
| Example | /users/42 |
/posts?page=2&sort=new |
Real-World Example: User Profile API
Let's build a small but realistic Express API to see both in action together:
const express = require("express");
const app = express();
// Mock data
const users = [
{ id: 1, name: "Aarav", city: "Mumbai", role: "admin" },
{ id: 2, name: "Priya", city: "Bangalore", role: "member" },
{ id: 3, name: "Rahul", city: "Mumbai", role: "member" },
{ id: 4, name: "Sneha", city: "Delhi", role: "admin" },
];
// GET a specific user by ID — URL param
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);
});
// GET all users with optional filters — query strings
app.get("/users", (req, res) => {
let result = [...users];
// Filter by city
if (req.query.city) {
result = result.filter(u =>
u.city.toLowerCase() === req.query.city.toLowerCase()
);
}
// Filter by role
if (req.query.role) {
result = result.filter(u => u.role === req.query.role);
}
// Sort by name
if (req.query.sort === "name") {
result.sort((a, b) => a.name.localeCompare(b.name));
}
res.json(result);
});
app.listen(3000);
Now test it:
GET /users/2
→ { id: 2, name: "Priya", city: "Bangalore", role: "member" }
GET /users?city=Mumbai
→ [Aarav, Rahul]
GET /users?role=admin
→ [Aarav, Sneha]
GET /users?city=Mumbai&sort=name
→ [Aarav, Rahul] sorted alphabetically
The URL param says which user. The query strings say which subset and how to order them.
Combining Both in One Route
This is very common in real APIs — especially for getting a subset of a resource:
// Get all orders for a specific user, with filters
app.get("/users/:userId/orders", (req, res) => {
const { userId } = req.params;
const { status, from, to, page = 1, limit = 10 } = req.query;
// userId identifies WHICH user
// status, from, to, page, limit FILTER the results
res.json({
user: userId,
filters: { status, from, to },
pagination: { page: parseInt(page), limit: parseInt(limit) }
});
});
GET /users/42/orders?status=delivered&page=2&limit=5
Result:
{
"user": "42",
"filters": { "status": "delivered", "from": undefined, "to": undefined },
"pagination": { "page": 2, "limit": 5 }
}
When to Use Which: The Decision Rule
Use a URL parameter when:
You're identifying a single, specific resource
The value is required for the route to make sense
Removing it would break the URL meaning
// ✅ Makes sense as URL param
app.get("/products/:id", ...) // specific product
app.get("/articles/:slug", ...) // specific article
app.get("/orders/:orderId", ...) // specific order
Use a query string when:
You're filtering, sorting, searching, or paginating
The value is optional
Multiple values work together as modifiers
// ✅ Makes sense as query string
app.get("/products", ...)
// /products?category=electronics&minRating=4&sort=price&page=1
The quick test:
"Does removing this value break the meaning of the URL?"
Yes → URL parameter
No → Query string
Common Mistakes to Avoid
❌ Using query strings where params should be
// Bad — the ID is required, not optional
app.get("/users", (req, res) => {
const id = req.query.id; // /users?id=42 ← awkward
});
// Good
app.get("/users/:id", (req, res) => {
const id = req.params.id; // /users/42 ← clean
});
❌ Forgetting query values are always strings
// Bug — comparing number to string
if (req.query.page === 1) { ... } // always false!
// Fix — parse it
if (parseInt(req.query.page) === 1) { ... } // ✅
❌ Not handling missing query params
// Could crash if sort is undefined
result.sort((a, b) => a[req.query.sort] - b[req.query.sort]);
// Safe version with a default
const sortBy = req.query.sort || "createdAt";
result.sort((a, b) => a[sortBy] - b[sortBy]);
SEO & URL Design Note
Good URL design also matters for readability and SEO:
✅ Clean, readable:
/blog/javascript-closures-explained
/users/42/settings
/products?category=laptops&sort=price
❌ Messy, opaque:
/blog?id=2847&slug=jce
/getUser?userId=42§ion=settings
URL params keep paths clean and RESTful. Query strings keep filtering flexible.
Summary
URL Parameter → /users/:id → req.params.id
Query String → /users?sort=asc → req.query.sort
Both together → /users/42/posts?limit=5
Params are for identity — which resource
Query strings are for modification — how to fetch it
Params are part of the route; query strings are always optional
Both arrive as strings in Express — parse numbers before using them
Once this clicks, your Express routes will be cleaner, more predictable, and much easier to maintain.
Found this useful? Follow for more Express.js and Node.js backend content. 🚀

