Skip to main content

Command Palette

Search for a command to run...

URL Parameters vs Query Strings in Express.js

Updated
12 min read
URL Parameters vs Query Strings in Express.js
H
CS undergrad | Tech enthusiast | Focusing on Web Dev • DSA • ML | Building skills for real-world impact

Every URL tells a story. Part of that story is built into the path itself. Another part is hidden in the query string. Learn the difference and you'll write cleaner, more predictable APIs.

This is about URL parameters, query strings, how to use them in Express, and when to pick one over the other.


What Are URL Parameters?

URL parameters are placeholders in the path itself. They're part of the route structure.

/users/123
        ^^^
        This is a URL parameter

The 123 is the parameter. It's the actual data embedded in the path.

How They Look

/users/123
/posts/42
/products/coffee-maker
/users/john/posts/15

The value changes, but the structure stays the same. You're identifying a specific resource.

In Express

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

When someone hits /users/123, req.params.userId equals "123".


What Are Query Strings?

Query strings come after the ? in the URL. They're optional filters or modifiers.

/users?role=admin&active=true
      ^^^^^^^^^^^^^^^^^^^^^^^^
      This is the query string

Query strings don't change the resource you're asking for. They modify how you get it.

How They Look

/users?role=admin
/posts?sort=date&order=desc
/products?category=electronics&price=100-500
/search?q=javascript&limit=10

Multiple query parameters are joined with &. Order doesn't matter.

In Express

app.get("/users", (req, res) => {
  const role = req.query.role;
  const active = req.query.active;
  res.json({ role, active });
});

When someone hits /users?role=admin&active=true, req.query contains:

{
  role: "admin",
  active: "true"
}

URL Structure Breakdown

Here's what a complete URL looks like:

https://example.com/users/123/posts/42?sort=date&limit=10
        |            |      |   |     |    |       |
        |            |      |   |     |    |       Query parameter
        |            |      |   |     |    Query parameter
        |            |      |   |     Query string (starts with ?)
        |            |      |   URL parameter
        |            |      URL parameter
        |            Path
        Domain

Breaking it down:

  • Domain: example.com
  • Path: /users/123/posts/42
  • URL Parameters: 123 and 42
  • Query String: sort=date&limit=10
  • Query Parameters: sort and limit

URL Parameters vs Query Strings

URL Parameters

For identifying a specific resource or navigating a hierarchy.

/users/123              <- Get user with ID 123
/posts/42               <- Get post with ID 42
/users/123/posts/42     <- Get post 42 belonging to user 123
/products/electronics/phones  <- Get phones in electronics category

Use URL parameters when:

  • You're identifying a specific resource
  • The parameter is essential to the request
  • You're navigating a hierarchy
  • The value is part of the resource's unique identity

Query Strings

For filtering, sorting, pagination, or optional modifications.

/users?role=admin              <- Get users, filter by role
/posts?sort=date&order=desc    <- Get posts, sort by date
/products?page=2&limit=20      <- Get products, pagination
/search?q=javascript           <- Search for "javascript"

Use query strings when:

  • You're filtering or searching
  • The parameter is optional
  • You're modifying how data is presented
  • Multiple parameters can apply simultaneously

Quick Comparison

Aspect URL Parameters Query Strings
Location In the path After ?
Purpose Identify resource Filter or modify
Essential Usually required Optional
Example /users/123 /users?role=admin
Multiple /users/123/posts/42 ?a=1&b=2&c=3
Order Fixed Doesn't matter
Best for IDs and hierarchy Filters and options

Accessing URL Parameters in Express

Single Parameter

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

Request: GET /users/123 Result: userId = "123"

Multiple Parameters

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

Request: GET /users/123/posts/42 Result: userId = "123", postId = "42"

Named Parameters

app.get("/products/:category/:productId", (req, res) => {
  const { category, productId } = req.params;
  res.json({ category, productId });
});

Request: GET /products/electronics/phone-456 Result: category = "electronics", productId = "phone-456"

Accessing All Parameters

app.get("/users/:userId/posts/:postId/comments/:commentId", (req, res) => {
  console.log(req.params);
  // { userId: "123", postId: "42", commentId: "789" }
  res.json(req.params);
});

Accessing Query Strings in Express

Single Query Parameter

app.get("/users", (req, res) => {
  const role = req.query.role;
  res.json({ role });
});

Request: GET /users?role=admin Result: role = "admin"

Multiple Query Parameters

app.get("/posts", (req, res) => {
  const sort = req.query.sort;
  const order = req.query.order;
  const limit = req.query.limit;
  res.json({ sort, order, limit });
});

Request: GET /posts?sort=date&order=desc&limit=10 Result: sort = "date", order = "desc", limit = "10"

Using Destructuring

app.get("/search", (req, res) => {
  const { q, category, page } = req.query;
  res.json({ q, category, page });
});

Request: GET /search?q=javascript&category=tutorials&page=2 Result: q = "javascript", category = "tutorials", page = "2"

Accessing All Query Parameters

app.get("/filter", (req, res) => {
  console.log(req.query);
  // All query parameters as an object
  res.json(req.query);
});

Request: GET /filter?a=1&b=2&c=3 Result:

{
  a: "1",
  b: "2",
  c: "3"
}

Handling Missing Parameters

Query parameters might not exist. Always check:

app.get("/products", (req, res) => {
  const page = req.query.page || 1;
  const limit = req.query.limit || 10;
  
  res.json({ page, limit });
});

Request: GET /products Result: page = 1, limit = 10


Real-World Examples

Example 1: User Profile

// Get a specific user
app.get("/users/:userId", (req, res) => {
  const userId = req.params.userId;
  const user = findUserById(userId);
  res.json(user);
});

Request: GET /users/123 Purpose: Get user 123's profile

Example 2: Search with Filters

// Search users with optional filters
app.get("/users/search", (req, res) => {
  const { q, role, active } = req.query;
  
  const results = searchUsers(q, {
    role,
    active
  });
  
  res.json(results);
});

Request: GET /users/search?q=john&role=admin&active=true Purpose: Search for "john", filter by role and active status

Example 3: Nested Resources

// Get posts by a specific user
app.get("/users/:userId/posts", (req, res) => {
  const userId = req.params.userId;
  const posts = getPostsByUser(userId);
  res.json(posts);
});

// Get a specific post by a specific user
app.get("/users/:userId/posts/:postId", (req, res) => {
  const { userId, postId } = req.params;
  const post = getPost(userId, postId);
  res.json(post);
});

Requests:

  • GET /users/123/posts → Get all posts by user 123
  • GET /users/123/posts/42 → Get post 42 by user 123

Example 4: Pagination and Sorting

// Get all products with optional pagination and sorting
app.get("/products", (req, res) => {
  const page = req.query.page || 1;
  const limit = req.query.limit || 20;
  const sort = req.query.sort || "name";
  const order = req.query.order || "asc";
  
  const products = getProducts({
    page,
    limit,
    sort,
    order
  });
  
  res.json(products);
});

Requests:

  • GET /products → Default pagination
  • GET /products?page=2&limit=10 → Page 2, 10 items
  • GET /products?sort=price&order=desc → Sort by price, descending

Example 5: Combined Parameters and Query

// Get posts by a user with optional sorting and pagination
app.get("/users/:userId/posts", (req, res) => {
  const userId = req.params.userId;
  const page = req.query.page || 1;
  const sort = req.query.sort || "date";
  
  const posts = getPostsByUser(userId, {
    page,
    sort
  });
  
  res.json(posts);
});

Requests:

  • GET /users/123/posts → Get user 123's posts, default sorting
  • GET /users/123/posts?sort=likes&page=2 → User 123's posts, sorted by likes, page 2

When to Use URL Parameters vs Query Strings

Use URL Parameters When

You're identifying a specific resource:

// Get specific user
app.get("/users/:userId", (req, res) => {
  // userId is essential
});

// Get specific post
app.get("/posts/:postId", (req, res) => {
  // postId is essential
});

// Hierarchical navigation
app.get("/users/:userId/posts/:postId", (req, res) => {
  // Both are essential to identify the resource
});

Use Query Strings When

You're filtering, sorting, or modifying results:

// Filter by role
app.get("/users", (req, res) => {
  // role is optional
});

// Sort results
app.get("/posts", (req, res) => {
  // sort is optional
});

// Pagination
app.get("/products", (req, res) => {
  // page and limit are optional
});

// Search
app.get("/search", (req, res) => {
  // q is optional but the endpoint makes sense without it
});

Decision Tree

Is this parameter essential to identify the resource?
├─ YES  → Use URL parameter
│   /users/123
│   /posts/42
│   /products/electronics/phones
│
└─ NO   → Use query string
    /users?role=admin
    /posts?sort=date
    /products?page=2

Complete Example: Blog API

Here's a real blog API using both URL parameters and query strings:

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

// Get all posts with optional filters
app.get("/posts", (req, res) => {
  const { author, status, page, limit, sort } = req.query;
  
  const posts = filterPosts({
    author,
    status,
    page: page || 1,
    limit: limit || 10,
    sort: sort || "date"
  });
  
  res.json(posts);
});

// Get a specific post
app.get("/posts/:postId", (req, res) => {
  const { postId } = req.params;
  const post = getPost(postId);
  
  if (!post) {
    return res.status(404).json({ error: "Post not found" });
  }
  
  res.json(post);
});

// Get comments on a post
app.get("/posts/:postId/comments", (req, res) => {
  const { postId } = req.params;
  const { page, limit, sort } = req.query;
  
  const comments = getComments(postId, {
    page: page || 1,
    limit: limit || 20,
    sort: sort || "date"
  });
  
  res.json(comments);
});

// Get a specific comment on a specific post
app.get("/posts/:postId/comments/:commentId", (req, res) => {
  const { postId, commentId } = req.params;
  const comment = getComment(postId, commentId);
  
  if (!comment) {
    return res.status(404).json({ error: "Comment not found" });
  }
  
  res.json(comment);
});

// Get posts by a specific author
app.get("/authors/:authorId/posts", (req, res) => {
  const { authorId } = req.params;
  const { page, status } = req.query;
  
  const posts = getPostsByAuthor(authorId, {
    page: page || 1,
    status
  });
  
  res.json(posts);
});

// Create a new post
app.post("/posts", (req, res) => {
  const { title, content, author } = req.body;
  const newPost = createPost({ title, content, author });
  res.status(201).json(newPost);
});

app.listen(3000);

Example Requests

GET /posts
→ Get all posts, default pagination

GET /posts?author=john&status=published
→ Get published posts by john

GET /posts?page=2&limit=5&sort=views
→ Get posts on page 2, 5 per page, sorted by views

GET /posts/42
→ Get post 42

GET /posts/42/comments
→ Get comments on post 42

GET /posts/42/comments?page=1&sort=likes
→ Get comments on post 42, sorted by likes

GET /posts/42/comments/789
→ Get comment 789 on post 42

GET /authors/john/posts
→ Get all posts by author john

GET /authors/john/posts?status=draft&page=1
→ Get draft posts by john, page 1

Practice Assignment

1. Create a user endpoint with URL parameters:

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

// Create this endpoint
app.get("/users/:userId", (req, res) => {
  // Get the userId from params
  // Return the user object
});

app.listen(3000);

2. Add a search endpoint with query strings:

// Create this endpoint
app.get("/users/search", (req, res) => {
  // Get query parameters: q, role, active
  // Return filtered results
});

3. Create a nested resource endpoint:

// Get all posts by a user
app.get("/users/:userId/posts", (req, res) => {
  // Use userId from params
  // Use page and limit from query
});

4. Combine parameters and query:

// Get a specific comment on a specific post by a specific user
app.get("/users/:userId/posts/:postId/comments/:commentId", (req, res) => {
  // Use all three parameters
  // Return the comment
});

5. Add filtering to a list endpoint:

// Get all products with filters
app.get("/products", (req, res) => {
  // Query parameters: category, minPrice, maxPrice, inStock
  // Return filtered products
});

Common Mistakes

Mistake 1: Confusing Parameters and Query

// WRONG - this looks like a parameter but it's a query value
app.get("/users/:userId", (req, res) => {
  // What if you want to filter by role?
  // You'd have to add another parameter
});

// RIGHT - separate the identifier from the filter
app.get("/users/:userId/posts", (req, res) => {
  // userId identifies the user
  // use query for filtering: ?sort=date&page=1
});

Mistake 2: Optional Parameters in the Path

// WRONG - optional parameters don't work well in paths
app.get("/users/:userId/:postId", (req, res) => {
  // What if someone hits /users/123? postId is missing
});

// RIGHT - use query strings for optional values
app.get("/users/:userId/posts", (req, res) => {
  // userId is required
  // ?page=2&limit=10 are optional
});

Mistake 3: Too Many Path Parameters

// WRONG - path becomes unreadable
app.get("/a/:b/:c/:d/:e/:f", (req, res) => {
  // Hard to understand what this endpoint does
});

// RIGHT - limit path parameters, use query for the rest
app.get("/users/:userId/posts/:postId/comments", (req, res) => {
  // Clear hierarchy
  // use query for filtering: ?sort=date&page=1
});

Mistake 4: Not Validating Parameter Types

// WRONG - userId could be anything
app.get("/users/:userId", (req, res) => {
  const userId = req.params.userId;
  // What if userId is "abc"? Should be a number
});

// RIGHT - validate the parameter
app.get("/users/:userId(\\d+)", (req, res) => {
  const userId = req.params.userId;
  // Now userId is guaranteed to be a number
});

Quick Recap

  • URL parameters are in the path. They identify a specific resource.

    • Example: /users/123, /posts/42
  • Query strings come after ?. They filter or modify results.

    • Example: /users?role=admin, /posts?sort=date
  • Use req.params to access URL parameters.

    • Example: req.params.userId
  • Use req.query to access query strings.

    • Example: req.query.role
  • Use parameters when: The value is essential and identifies a resource.

  • Use query strings when: The value is optional and filters or modifies results.

  • Parameters go in order: /users/:userId/posts/:postId

  • Query strings don't need to be in order: ?page=2&sort=name is the same as ?sort=name&page=2

  • Always validate parameter types and check if query parameters exist.

  • A good API uses both: parameters for hierarchy and identity, query strings for options.

Get the distinction right and your API routes become clear, predictable, and easy to use.

Happy coding! 🚀


If you enjoyed this article, check out my other blogs on this profile. 🔗 Connect with me: LinkedIn | GitHub | X (Twitter)