Skip to main content

Command Palette

Search for a command to run...

URL Parameters vs Query Strings in Express.js — What's the Difference and When to Use Each

Published
7 min read

Every URL tells a story. Take this one:

https://api.myapp.com/users/42/posts?sort=latest&limit=10

There are two distinct pieces of dynamic information packed in there — 42 is a URL parameter, and sort=latest&limit=10 is a query string. They look similar at a glance, but they serve completely different purposes, and mixing them up leads to APIs that feel inconsistent and awkward to use.

Let's break both down from the ground up.


1. What URL Parameters Are

A URL parameter (also called a route parameter or path parameter) is a dynamic segment embedded directly inside the URL path. It acts as an identifier — it points to a specific resource.

https://api.myapp.com/users/42
                              ^^
                              This is a URL parameter

Think of the URL path as a filing system. /users/42 means: "Go to the users section, pull out the record with ID 42." The number 42 is not a filter or an option — it's the address of a specific thing.

More examples:

/products/shoes-nike-air-max       ← a specific product by slug
/orders/ORD-2024-00891             ← a specific order by reference number
/countries/IN/states/WB            ← nested resource: state within a country

The defining trait of a URL parameter: remove it, and the URL no longer points to a valid resource. /users/ without an ID doesn't mean anything meaningful.


2. What Query Parameters Are

A query string appears after the ? at the end of a URL. It's a collection of key-value pairs separated by &, and it acts as a set of filters, modifiers, or options that shape what you get back from a resource.

https://api.myapp.com/posts?sort=latest&category=nodejs&limit=10
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                            This is the query string

Where a URL parameter identifies which resource, query parameters describe how you want it. They answer questions like:

  • In what order should results be sorted?

  • How many results per page?

  • Filter by which category or date range?

  • What search term was entered?

More examples:

/products?color=red&size=M&price_max=2000
/users?role=admin&active=true
/search?q=express+middleware&page=2

The defining trait of a query parameter: remove it, and the URL still points to the same resource — you just get a different (usually broader) view of it. /posts without filters still makes sense.


3. The Differences Between Them

Here's a side-by-side breakdown:

┌─────────────────────┬───────────────────────────┬────────────────────────────────┐
│                     │   URL Parameters            │   Query Parameters             │
├─────────────────────┼───────────────────────────┼────────────────────────────────┤
│ Location in URL     │ Inside the path            │ After the ? symbol             │
│ Syntax              │ /users/:id                 │ /users?role=admin              │
│ Purpose             │ Identify a resource        │ Filter, sort, or modify        │
│ Required?           │ Usually yes                │ Usually optional               │
│ Readable without it?│ No — URL breaks            │ Yes — resource still exists    │
│ Example             │ /posts/99                  │ /posts?limit=5&sort=asc        │
└─────────────────────┴───────────────────────────┴────────────────────────────────┘

A concrete way to think about it:

  • URL parameter = the noun. Which user? Which post? Which product?

  • Query parameter = the adjective. How many? In what order? Filtered by what?


4. Accessing URL Parameters in Express

In Express, you define URL parameters in your route by prefixing a segment with a colon (:). Express then populates req.params with the actual values from the incoming request.

// Route definition — :userId is a named parameter
app.get("/users/:userId", (req, res) => {
  const { userId } = req.params;
  res.json({ message: `Fetching user with ID: ${userId}` });
});

Request: GET /users/42 Result: req.params = { userId: "42" }

You can have multiple parameters in a single route:

app.get("/countries/:country/states/:state", (req, res) => {
  const { country, state } = req.params;
  res.json({ country, state });
});

Request: GET /countries/IN/states/WB Result: req.params = { country: "IN", state: "WB" }

One important note: req.params values are always strings, even if the value looks like a number. Parse them before use:

app.get("/posts/:postId", async (req, res) => {
  const postId = parseInt(req.params.postId, 10);

  if (isNaN(postId)) {
    return res.status(400).json({ error: "Invalid post ID" });
  }

  const post = await Post.findById(postId);
  if (!post) return res.status(404).json({ error: "Post not found" });

  res.json(post);
});

5. Accessing Query Strings in Express

Query parameters are available on req.query — Express parses the query string automatically and exposes it as a plain JavaScript object. No extra configuration needed.

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

Request: GET /posts?sort=latest&category=nodejs&limit=10 Result: req.query = { sort: "latest", category: "nodejs", limit: "10" }

Since query params are optional, always provide sensible defaults:

app.get("/posts", async (req, res) => {
  const sort    = req.query.sort     || "latest";
  const limit   = parseInt(req.query.limit, 10) || 20;
  const page    = parseInt(req.query.page, 10)  || 1;
  const category = req.query.category; // optional — may be undefined

  const filter = {};
  if (category) filter.category = category;

  const posts = await Post.find(filter)
    .sort(sort)
    .limit(limit)
    .skip((page - 1) * limit);

  res.json({ page, limit, sort, posts });
});

Request: GET /posts?sort=oldest&limit=5&page=2&category=nodejs

This pattern — defaulting, parsing, and optionally applying filters — is the standard way to build a paginated, filterable list endpoint.


6. When to Use Params vs Query

Here's the practical decision framework:

Use a URL parameter when you're identifying a specific resource:

GET /users/42              ← fetch user #42
GET /products/iphone-15    ← fetch this specific product
GET /invoices/INV-0042     ← fetch a specific invoice
DELETE /sessions/abc123    ← delete a specific session

Use query parameters when you're modifying or filtering a collection:

GET /users?role=admin&active=true        ← filter the users list
GET /products?color=red&maxPrice=5000    ← filter by attributes
GET /posts?sort=oldest&limit=5&page=3   ← paginate a feed
GET /search?q=express+tutorial           ← search query

A combined example — fetching a user's posts with filters:

GET /users/42/posts?sort=latest&limit=5
         ^^                              ← param: identifies the user
                   ^^^^^^^^^^^^^^^^^^^^^  ← query: modifies the result set

The route in Express:

app.get("/users/:userId/posts", async (req, res) => {
  const userId = parseInt(req.params.userId, 10);
  const sort   = req.query.sort  || "latest";
  const limit  = parseInt(req.query.limit, 10) || 10;

  const posts = await Post.find({ authorId: userId })
    .sort(sort)
    .limit(limit);

  res.json({ userId, sort, limit, posts });
});

This URL is both precise (it identifies whose posts) and flexible (the caller controls sorting and pagination). That's the hallmark of a well-designed route.


URL Anatomy: The Full Picture

  https://api.myapp.com/users/42/posts?sort=latest&limit=5&page=1
  ──────  ─────────────  ──────────── ─ ─────────────────────────
  scheme      host         path        ?        query string
                            │                       │
              ┌─────────────┴──────┐    ┌───────────┴────────────┐
              │  URL Parameters    │    │    Query Parameters     │
              │                    │    │                         │
              │  /users/:userId    │    │  sort=latest            │
              │  /posts            │    │  limit=5                │
              │                    │    │  page=1                 │
              └────────────────────┘    └─────────────────────────┘
              Identify the resource       Modify the response

Quick Reference

// Route: /users/:userId/posts
// Request: GET /users/42/posts?sort=latest&limit=5

app.get("/users/:userId/posts", (req, res) => {
  console.log(req.params);  // { userId: "42" }
  console.log(req.query);   // { sort: "latest", limit: "5" }
});

Key Takeaways

  • URL parameters live inside the path and identify a specific resource. Without them, the URL is incomplete.

  • Query parameters appear after ? and modify how a resource or collection is returned. They are almost always optional.

  • Access URL params via req.params and query params via req.query in Express — both return strings, so always parse numbers explicitly.

  • Combine both in the same route when you need precision (which resource?) and flexibility (how should it be returned?).

  • The cleaner the distinction in your routes, the more intuitive your API feels to consume.

Nail this distinction early, and your REST API design will be consistently clean across every endpoint you build.