Skip to main content

Command Palette

Search for a command to run...

URL Parameters vs Query Strings in Express.js

Updated
8 min read
URL Parameters vs Query Strings in Express.js

In this blog, we would be learning about url parameters, query string and how they are different from each other in Express.js. The prerequisite for this blog is that you are aware of Express.js and the ways it simplifies complexity. In case you are not or you want to brush things up, go through this blog.


What's the problem we are solving ?

You must be aware of REST apis, in the rest apis we construct URI to target a particular resource of our system, if you are finding it hard to grasp, then I highly recommend you this blog.

When you are targeting resources like Users, Posts, you might not always need the complete resource itself, but something unique in the resource itself, it could be a particular resource or similar type of resources in the whole resource.

Now to achieve this, we make sure, we pass something with our URI which shall indicate the REST api to not return the complete resource but do some operations on it before sending the response.

This is where URL Params and URL Query Params come into the picture.

They sound similar at first, but they serve very different purposes.

What are URL parameters (Params)?

URL params are nothing but a part of the URI itself. They are used to identify a specific resource.

e.g. you want to get a user whose id is 35, then you are indicating that you don't want all the users, but a user from all users who has a distinction of id as 35.

GET /users/35

Here:

  • 35 is a URL parameter

  • It identifies a specific user

The same thing could be written in express as :

app.get("/users/:id", (req, res) => {
  const userId = req.params.id;
  res.send(`User ID: ${userId}`);
});

req.params contains values extracted from the URL path.

What are Query parameters?

You can understand from the term that is used in the name itself, Query parameters are all about queries. You don't need a specific unique data, but you need data(s) which fulfil certain conditions.

Now the rule to know about that condition is something that we can send even through the payload but we use query parametersto send the data.

Query params come after ? in the URL. They are used to filter, sort, or modify data.

E.g: you want user (s) whose age is 25 and want them sorted in the ascending order.

GET /users?age=25&sort=asc

Here:

  • age=25 → filter

  • sort=asc → modifier

If you are trying to have more than on query parameter then you use & between them. The query parameters begin with ?.

You can do this is express as ;

app.get("/users", (req, res) => {
  const age = req.query.age;
  const sort = req.query.sort;

  res.send(`Age: \({age}, Sort: \){sort}`);
});

req.query contains query parameters.

Key difference

Now we shall not dive into theory but directly understand what is different between the two and when to use what.

  • When you are wanting the exact resource, then you use url params. Remember Params = Identity.

  • When you want data which passes certain conditions or want filtered data or modified data, you use query params
    Remember Query = Behaviour.

Side - By - Side comparison

Feature URL Params Query Params
Location Path (/users/:id) After ? (?key=value)
Purpose Identify resource Filter / modify data
Required Usually required Usually optional
Usage req.params req.query
Example /users/42 /users?age=25

Real-world examples

1. User profile (Params)

GET /users/101
  • Fetch one specific user

  • 101 is essential → without it, request doesn’t make sense

2. Search filters (Query)

GET /products?category=books&price=low
  • Fetch many products

  • Query modifies results

3. Combining both (very common)

GET /users/101/posts?limit=10&sort=desc
  • 101 → which user (param)

  • limit, sort → how to fetch posts (query)

When to use Params vs Query

Use Params when:

  • You are targeting a specific resource

  • The value is mandatory

  • Example:

    • /users/:id

    • /orders/:orderId

Use Query when:

  • You are filtering or modifying results

  • Values are optional

  • Example:

    • /users?age=25

    • /products?sort=price

Output of query and url parameters

You might wonder when you do

GET /users/42?isActive=false&age=25
  • 42 → part of the path

  • true, 25 → part of the query string

From the server’s perspective, all of these are just strings in the URL, not typed values.

const id = req.params.id
const isActive = req.query.isActive
id        // "42"
isActive  // "false"

JavaScript will not automatically convert types for you, so this can cause subtle bugs.

Common Bugs using query and url parameters

From the above code block if you do:

if (req.query.isActive) {
  // "false" is truthy → this block still runs!
}

You need to explicitly convert types based on your expectation.

Number conversion

const id = Number(req.params.id)
const age = parseInt(req.query.age, 10)

Boolean conversion

const isActive = req.query.isActive === "true"

This is a mistake most developers do, be careful while using them.

Advance Query Formats

Up until now, you have studied, the basic query formats like:

/users?role=admin&isActive=true

It's time we look into something not so basic

Array Queries

If you do something like:

/users?role=admin&role=user

OR

/users?role[]=admin&role[]=user
req.query = {
  role: ["admin", "user"]
}

This useful when filtering multiple categories, tags, roles e.t.c.

Nested query objects

/users?filter[age]=25&filter[isActive]=true

This is a nice way to structure your API, instead of keeping it flat. The above code block shall look like:

req.query = {
  filter: {
    age: "25",
    isActive: "true"
  }
}

this is particularly useful in filtering purpose:

/users?age[gte]=18&age[lte]=30
req.query = {
  age: {
    gte: "18",
    lte: "30"
  }
}

This is a developer way of putting structure where age>=18 and age<=30. Other common operators:

  • gt → greater than

  • gte → greater than equal to

  • lt → less than

  • ne → not equal

  • in → matches any value

You can combine all of them in a simple example:

/products?price[lt]=1000&category[in]=electronics,books
req.query = {
  price: {
    lt: "1000"
  },
  category: {
    in: "electronics,books"
  }
}

But if you do something like:

/products?price[lt]=1000&category[in]=electronics&category[in]=books
req.query = {
  category: {
    in: ["electronics", "books"]
  }
}

Advance URL parameters

You are aware of basic entensions like:

app.get('/users/:userId/posts/:postId', handler)
/users/42/posts/99
req.params = {
  userId: "42",
  postId: "99"
}

Optional params

You can make params optional using ?:

app.get('/users/:id?', handler)

The above code block will match:

/users
/users/42

Regex constraints on params

You can restrict what a param accepts:

app.get('/users/:id(\\d+)', handler)

Matches:

/users/123   ✅
/users/abc   ❌

This is great for:

  • ensuring numeric IDs

  • avoiding invalid routes early

Hyphen & dot separated params

You can combine params in a single segment:

app.get('/users/:id-:slug', handler)

Request:

/users/42-john-doe
req.params = {
  id: "42",
  slug: "john-doe"
}

Another example:

app.get('/files/:name.:ext', handler)
/files/report.pdf
req.params = {
  name: "report",
  ext: "pdf"
}

This is very useful in:

  • SEO URLs

  • file handling

Why not send things in payload (Personal Experience) ?

As a beginner, url params and query parameters used to be confusing to me so, i chose the simplest path available, I used to send things in payload itself. It was later I realised I was using a hammer for every job and lost the advantage that HTTP and URLs give me. Sending a payload data was another unnecessary weight on the server. Let me explain:

Different parts of a request exist for different responsibilities:

  • URL params → identity

  • Query params → filtering/modifying

  • Body → actual data payload

Mixing them all into the body removes that clarity.

  1. GET requests don't use body (by convention): technically it's possible but many tools don't support it, so doing the below might now work:

    GET /users
    Body: { "id": 42 }
    

    So it recommended to use:

    GET /users/42
    
  2. URLs become meaningful, they can be bookmarked and makes sense easily

  3. Caching works better: Systems like CDNs and browsers cache based on URL, not body.

    GET /products?category=books
    

    The above URL is cacheable, however sending it as a POST req and data inside the body won't cache it.

  4. REST design patterns: If you send things in payload, you are openly violating the REST principles, it might work fine for small projects, but it get's hard to standardize over time.

  5. Only when needed: Send data in request body only when you are performing creation/updation of data or data is complex or large i.e. when needed.

Conclusion

This blog might be covering a simple topic, but this is the base of good design choices that you make when creating an application using Express. In further blogs, we would be advancing more into Express and other design patterns while creating a web application.

Happy Coding !