DEV Community

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

Posted on

URL Parameters vs Query Strings in Express.js

Two ways to pass data through URLs — and knowing when to use each one.


When I first started building APIs with Express, I kept confusing two things: when should the data go in the URL, and when should it go after the URL? Like, what's the difference between these two requests?

/users/42
/users?id=42
Enter fullscreen mode Exit fullscreen mode

Both pass the number 42 to the server. Both work. But they mean fundamentally different things, and using the wrong one leads to messy, confusing APIs that other developers (and your future self) will hate.

The answer came down to a simple mental model I picked up in the ChaiCode Web Dev Cohort 2026: URL parameters identify what you want. Query strings modify how you want it. Once that clicked, I never confused them again.

Let me show you.


URL Structure Breakdown

Before diving into the differences, let's understand what a URL actually looks like:

https://api.example.com/users/42/orders?status=shipped&page=2
└─┬──┘  └──────┬──────┘└────┬────┘└─┬─┘ └──────────┬──────────┘
scheme      host        path     params    query string
Enter fullscreen mode Exit fullscreen mode

Let's zoom in on the parts we care about:

/users/42/orders?status=shipped&page=2
│      │   │      │               │
│      │   │      └── query       └── query
│      │   │          parameter       parameter
│      │   │          key=value       key=value
│      │   │
│      │   └── path segment
│      │
│      └── URL parameter (dynamic — changes per request)
│
└── path segment (static — always "users")
Enter fullscreen mode Exit fullscreen mode

Two different mechanisms in the same URL. Let me explain each one.


What Are URL Parameters?

URL parameters (also called route parameters or path parameters) are dynamic segments inside the URL path. They're placeholders for specific values — typically identifiers.

In Express, you define them with a colon (:):

app.get("/users/:id", (req, res) => {
  console.log(req.params.id); // whatever value is in that position
});
Enter fullscreen mode Exit fullscreen mode

Examples

Route definition: /users/:id

  /users/1       req.params.id = "1"
  /users/42      req.params.id = "42"
  /users/abc     req.params.id = "abc"
Enter fullscreen mode Exit fullscreen mode
Route definition: /products/:category/:productId

  /products/electronics/500
     req.params.category = "electronics"
     req.params.productId = "500"

  /products/books/101
     req.params.category = "books"
     req.params.productId = "101"
Enter fullscreen mode Exit fullscreen mode

Key Characteristics

  • Part of the URL path (before the ?)
  • Defined with :name in the route definition
  • Accessed via req.params
  • Represent what resource you're requesting
  • Usually required — the route won't match without them

Real-World URL Parameters

GET /users/42                    → Get user with ID 42
GET /posts/15                    → Get post with ID 15
GET /users/42/orders             → Get all orders for user 42
GET /products/electronics/500    → Get product 500 in electronics
DELETE /comments/789             → Delete comment with ID 789
Enter fullscreen mode Exit fullscreen mode

Notice the pattern: every URL parameter is an identifier — it points to a specific resource.


What Are Query Parameters?

Query parameters (also called query strings) are key-value pairs that come after the ? in a URL. They're used for filtering, sorting, searching, and modifying how the server processes the request.

In Express, you access them via req.query:

app.get("/users", (req, res) => {
  console.log(req.query); // { role: "admin", page: "2" }
});
Enter fullscreen mode Exit fullscreen mode

Examples

URL: /users?role=admin

  req.query.role = "admin"
Enter fullscreen mode Exit fullscreen mode
URL: /products?category=electronics&minPrice=100&maxPrice=500&sort=price

  req.query.category = "electronics"
  req.query.minPrice = "100"
  req.query.maxPrice = "500"
  req.query.sort = "price"
Enter fullscreen mode Exit fullscreen mode

Key Characteristics

  • Come after the ? in the URL
  • Format: key=value, separated by &
  • Accessed via req.query
  • Represent how to filter, sort, or modify the response
  • Always optional — the route works without them

Real-World Query Parameters

GET /users?role=admin&active=true       → Get active admin users
GET /products?sort=price&order=asc      → Get products sorted by price
GET /search?q=nodejs&page=3&limit=20    → Search "nodejs", page 3, 20 results
GET /orders?status=shipped&from=2026-01 → Get shipped orders from January
GET /posts?tag=javascript               → Get posts tagged "javascript"
Enter fullscreen mode Exit fullscreen mode

Notice the pattern: every query parameter modifies the response — it doesn't change what resource you're requesting, it changes how that resource is filtered or presented.


Params vs Query — Comparison Diagram

┌───────────────────────────────────────────────────────────┐
│                                                           │
│  URL PARAMETERS                  QUERY PARAMETERS         │
│  (identifiers)                   (modifiers)              │
│                                                           │
│  /users/42                       /users?role=admin        │
│        ↑                               ↑                 │
│    "WHICH user?"                 "WHICH KIND of users?"   │
│    → A specific one              → A filtered list        │
│                                                           │
│  In the PATH                     After the ?              │
│  req.params                      req.query                │
│  Required                        Optional                 │
│  Identifies a resource           Modifies the response    │
│                                                           │
│  Think: NOUN                     Think: ADJECTIVE         │
│  "Give me user 42"               "Give me admin users"    │
│                                                           │
└───────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Accessing Params in Express

Single Parameter

// Route: /users/:id
app.get("/users/:id", (req, res) => {
  const userId = req.params.id;

  // Find user (simulated)
  const user = users.find((u) => u.id === parseInt(userId));

  if (!user) {
    return res.status(404).json({ error: "User not found" });
  }

  res.json(user);
});

// GET /users/1   → { id: 1, name: "Pratham", role: "developer" }
// GET /users/999 → { error: "User not found" }
Enter fullscreen mode Exit fullscreen mode

Multiple Parameters

// Route: /users/:userId/posts/:postId
app.get("/users/:userId/posts/:postId", (req, res) => {
  const { userId, postId } = req.params;

  res.json({
    message: `Fetching post ${postId} by user ${userId}`,
  });
});

// GET /users/5/posts/12
// → { message: "Fetching post 12 by user 5" }
Enter fullscreen mode Exit fullscreen mode

Important: Params Are Always Strings

app.get("/users/:id", (req, res) => {
  console.log(typeof req.params.id); // "string" — always!

  // Convert to number when needed
  const id = parseInt(req.params.id);

  // Or check if it's a valid number
  if (isNaN(id)) {
    return res.status(400).json({ error: "Invalid user ID" });
  }

  // Now use the numeric id
});
Enter fullscreen mode Exit fullscreen mode

Accessing Query Strings in Express

Basic Query

app.get("/search", (req, res) => {
  const searchTerm = req.query.q;
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 10;

  res.json({
    searching: searchTerm,
    page,
    limit,
    message: `Showing ${limit} results for "${searchTerm}" (page ${page})`,
  });
});

// GET /search?q=express&page=2&limit=5
// → { searching: "express", page: 2, limit: 5, message: "..." }

// GET /search?q=nodejs
// → { searching: "nodejs", page: 1, limit: 10, message: "..." }
// (page and limit use defaults when not provided)
Enter fullscreen mode Exit fullscreen mode

Filtering a Collection

const products = [
  { id: 1, name: "Laptop", category: "electronics", price: 75000 },
  { id: 2, name: "Shirt", category: "clothing", price: 1500 },
  { id: 3, name: "Phone", category: "electronics", price: 25000 },
  { id: 4, name: "Jeans", category: "clothing", price: 2500 },
  { id: 5, name: "Headphones", category: "electronics", price: 3000 },
];

app.get("/api/products", (req, res) => {
  let result = [...products];

  // Filter by category (optional)
  if (req.query.category) {
    result = result.filter((p) => p.category === req.query.category);
  }

  // Filter by max price (optional)
  if (req.query.maxPrice) {
    result = result.filter((p) => p.price <= parseInt(req.query.maxPrice));
  }

  // Sort by field (optional)
  if (req.query.sort) {
    result.sort((a, b) => {
      if (req.query.order === "desc") return b[req.query.sort] - a[req.query.sort];
      return a[req.query.sort] - b[req.query.sort];
    });
  }

  res.json(result);
});

// GET /api/products
// → All 5 products

// GET /api/products?category=electronics
// → Laptop, Phone, Headphones

// GET /api/products?category=electronics&maxPrice=10000
// → Phone, Headphones

// GET /api/products?sort=price&order=desc
// → Sorted by price, highest first
Enter fullscreen mode Exit fullscreen mode

Every query parameter is optional. The route works without any of them and returns all products. Add query parameters to narrow down the results.

Important: Query Values Are Always Strings

app.get("/example", (req, res) => {
  // GET /example?count=5&active=true

  console.log(typeof req.query.count); // "string"
  console.log(typeof req.query.active); // "string"

  // Convert as needed:
  const count = parseInt(req.query.count); // 5 (number)
  const active = req.query.active === "true"; // true (boolean)
});
Enter fullscreen mode Exit fullscreen mode

When to Use Params vs Query — Decision Guide

Scenario Use Example
Get a specific user URL Param GET /users/42
Get all users filtered by role Query String GET /users?role=admin
Get a specific product URL Param GET /products/101
Search products by keyword Query String GET /products?search=laptop
Get a specific order URL Param GET /orders/555
Get orders filtered by status Query String GET /orders?status=shipped
Get a user's specific post URL Params GET /users/42/posts/7
Get a user's posts sorted by date Both GET /users/42/posts?sort=date
Paginate a list Query String GET /posts?page=3&limit=20
Delete a specific comment URL Param DELETE /comments/89

The Simple Rule

Ask yourself: "Am I identifying a SPECIFIC resource?"
  → YES → URL Parameter (/users/42)
  → NO  → Query String (/users?role=admin)

Or even simpler:
  → REQUIRED identifier → URL Param
  → OPTIONAL modifier   → Query String
Enter fullscreen mode Exit fullscreen mode

Both Together — Common Pattern

Most real APIs use both in the same request:

// URL param identifies WHICH user
// Query string modifies WHICH posts to return
app.get("/users/:id/posts", (req, res) => {
  const userId = req.params.id; // which user
  const { tag, sort, page } = req.query; // how to filter posts

  res.json({
    user: userId,
    filters: { tag, sort, page },
    message: `Posts by user ${userId}, filtered by tag="${tag}", sorted by ${sort}, page ${page}`,
  });
});

// GET /users/42/posts?tag=javascript&sort=newest&page=2
// → Posts by user 42, tagged "javascript", newest first, page 2
Enter fullscreen mode Exit fullscreen mode

URL params tell you what. Query strings tell you how.


Common Mistakes

❌ Using Query Strings for Resource Identification

BAD:  GET /users?id=42        ← id is required, should be a param
GOOD: GET /users/42            ← clean, RESTful
Enter fullscreen mode Exit fullscreen mode

❌ Using URL Params for Optional Filters

BAD:  GET /users/role/admin    ← what if no filter is needed?
GOOD: GET /users?role=admin    ← optional, clean
Enter fullscreen mode Exit fullscreen mode

❌ Putting Everything in Query Strings

BAD:  GET /api?resource=users&id=42&action=posts&postId=7
GOOD: GET /api/users/42/posts/7
Enter fullscreen mode Exit fullscreen mode

❌ Forgetting to Parse Types

// Query values are always strings!
// GET /products?minPrice=100

// ❌ This comparison fails (string vs number)
if (product.price > req.query.minPrice) { ... }

// ✅ Parse the string to a number first
if (product.price > parseInt(req.query.minPrice)) { ... }
Enter fullscreen mode Exit fullscreen mode

Let's Practice: Hands-On Assignment

Part 1: URL Parameters

const express = require("express");
const app = express();

const users = [
  { id: 1, name: "Pratham", role: "developer" },
  { id: 2, name: "Arjun", role: "designer" },
  { id: 3, name: "Priya", role: "manager" },
];

// Get user by ID
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);
});

app.listen(3000);

// Test: /users/1, /users/2, /users/99
Enter fullscreen mode Exit fullscreen mode

Part 2: Query Strings

// Get users with optional filtering
app.get("/users", (req, res) => {
  let result = [...users];

  if (req.query.role) {
    result = result.filter((u) => u.role === req.query.role);
  }

  if (req.query.search) {
    result = result.filter((u) =>
      u.name.toLowerCase().includes(req.query.search.toLowerCase()),
    );
  }

  res.json({ count: result.length, users: result });
});

// Test: /users, /users?role=developer, /users?search=ar
Enter fullscreen mode Exit fullscreen mode

Part 3: Combine Both

const posts = [
  { id: 1, userId: 1, title: "Learning Node.js", tag: "backend" },
  { id: 2, userId: 1, title: "Express Routing", tag: "backend" },
  { id: 3, userId: 2, title: "UI Design Tips", tag: "design" },
  { id: 4, userId: 1, title: "Async Patterns", tag: "backend" },
];

app.get("/users/:id/posts", (req, res) => {
  const userId = parseInt(req.params.id);
  let userPosts = posts.filter((p) => p.userId === userId);

  if (req.query.tag) {
    userPosts = userPosts.filter((p) => p.tag === req.query.tag);
  }

  res.json({
    userId,
    totalPosts: userPosts.length,
    posts: userPosts,
  });
});

// Test: /users/1/posts
//       /users/1/posts?tag=backend
//       /users/2/posts
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  1. URL parameters (:id) are dynamic segments in the URL path. They identify which specific resource you want. Accessed via req.params.
  2. Query parameters (?key=value) come after the ? in the URL. They filter, sort, or modify the response. Accessed via req.query.
  3. Params = identifiers, Query = modifiers. "Give me user 42" vs "Give me admin users."
  4. Both values are always strings. Parse to numbers or booleans as needed with parseInt() or === "true".
  5. Use params for required resource identification. Use query strings for optional filtering, sorting, pagination, and search.

Wrapping Up

The difference between URL parameters and query strings is one of those things that seems minor but shapes how clean and intuitive your API is. Using the right one in the right situation makes your routes readable, your API RESTful, and your code easy to maintain. Use params to identify. Use queries to modify. Combine them for powerful, flexible endpoints.

I'm learning all of this through the ChaiCode Web Dev Cohort 2026 under Hitesh Chaudhary and Piyush Garg. Getting routing right early — especially the param vs query distinction — saves you from massive refactors later. It's a small detail with big impact.

Connect with me on LinkedIn or visit PrathamDEV.in. More articles on the way.

Happy coding! 🚀


Written by Pratham Bhardwaj | Web Dev Cohort 2026, ChaiCode

Top comments (0)