Skip to content

Express Query Parameters

Query parameters, the part of a URL after the ?, are how clients pass optional filtering, sorting, and pagination information. This lesson covers how Express parses them into req.query.

What Are Query Parameters?

A query string is a set of key=value pairs appended to a URL after a ?, separated by &. Express automatically parses this into a plain object available as req.query, no extra middleware required.

Unlike route parameters, query parameters are always optional by nature, a good API should behave sensibly whether or not any are provided.

// GET /products?category=shoes&page=2&sort=price
app.get('/products', (req, res) => {
  console.log(req.query);
  // { category: 'shoes', page: '2', sort: 'price' }
  res.json(req.query);
});

All values in req.query are strings (or arrays of strings), just like req.params, cast numeric values before using them in comparisons.

Query String Parsing Syntax

?key=value
?a=1&b=2
?tags=red&tags=blue      // req.query.tags === ['red', 'blue']
?filter[status]=active   // req.query.filter === { status: 'active' }
  • Repeating the same key produces an array on req.query.
  • Bracket syntax (filter[status]=active) produces a nested object, via the qs library Express uses internally.
  • Missing query parameters are simply undefined, always provide sensible defaults.
  • Query parameters are always strings until you explicitly cast them.

Query Parameters Cheatsheet

Common filtering, sorting, and pagination patterns built on req.query.

URL req.query Use Case
/products?page=2&limit=20 { page: '2', limit: '20' } Pagination
/products?sort=-price { sort: '-price' } Sorting (leading - for descending)
/products?category=shoes { category: 'shoes' } Filtering
/products?tags=a&tags=b { tags: ['a', 'b'] } Multi-value filtering
/products?q=running+shoes { q: 'running shoes' } Free-text search

Pagination With Query Parameters

A typical paginated endpoint reads page and limit from the query string, applies sane defaults, and clamps them to reasonable bounds before using them.

app.get('/products', (req, res) => {
  const page = Math.max(1, Number(req.query.page) || 1);
  const limit = Math.min(100, Math.max(1, Number(req.query.limit) || 20));
  const start = (page - 1) * limit;

  const results = allProducts.slice(start, start + limit);

  res.json({
    page,
    limit,
    total: allProducts.length,
    data: results,
  });
});

Filtering and Sorting

Build filtering logic defensively, only apply a filter when the corresponding query parameter is actually present, so an endpoint still returns everything by default.

app.get('/products', (req, res) => {
  let results = [...allProducts];

  if (req.query.category) {
    results = results.filter((p) => p.category === req.query.category);
  }

  if (req.query.sort === 'price') {
    results.sort((a, b) => a.price - b.price);
  }

  res.json(results);
});

Common Mistakes

  • Comparing req.query.page to a number with === without casting it first, since it is always a string.
  • Not setting an upper limit on pagination size, letting a client request thousands of records at once.
  • Assuming a query parameter always exists instead of providing a sensible default.
  • Forgetting that repeated query keys produce arrays and can break code that expects a single string.

Key Takeaways

  • Express automatically parses the query string into req.query, no middleware needed.
  • All query values are strings or arrays of strings, cast them explicitly when needed.
  • Always provide defaults and reasonable limits for pagination parameters.
  • Apply filters conditionally so an endpoint behaves sensibly with no query parameters at all.

Pro Tip

Cap limit on paginated endpoints (e.g. Math.min(100, requestedLimit)) so a malicious or careless client can never force your API to return an unbounded number of records in one request.