Skip to main content

Command Palette

Search for a command to run...

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.

Updated
7 min read
URL Parameters vs Query Strings in Express.js
C
Software developer passionate about building scalable web applications with React and backend technologies. I enjoy solving problems, building projects, and sharing my learning with the community.

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&section=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 identitywhich resource

  • Query strings are for modificationhow 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. 🚀

More from this blog

Why Node.js is Perfect for Building Fast Web Applications

Every technology makes a bet. Node.js's bet was this: most web applications aren't slow because they do too much computation. They're slow because they spend most of their time waiting — waiting for a database to respond, waiting for a file to load, waiting for an external API to return. If you build a runtime optimised around that specific reality, you get something genuinely fast for the work most web apps actually do.

May 9, 202611 min read1
C

Chetan Chauhan | Tech Blog | chetan71

41 posts