Back to Blog
Lesson 22 of the REST API Design: Design Your First Clean REST API course
API ArchitectureAugust 8, 20264 min read

Filtering Collections: How to Parse Query Params in REST APIs

Learn how to implement filtering for your REST API collections. Master query parameter parsing to allow users to fetch tasks by status like 'completed'.

REST APIAPI DesignFilteringQuery ParamsNode.jsBackend Engineering
A young woman rests her head on a stack of books in a cozy library setting.

Previously in this course, we covered the Introduction to Query Parameters: Modifying API Behavior to understand how they can change request behavior without altering resource paths. In this lesson, we are moving from theory to implementation by adding functional filtering to our Task Manager API.

When working with collections—as discussed in URI Hierarchy and Collections: Designing Clean REST API Paths—you'll quickly find that returning every single record is rarely what the client needs. By implementing filtering, you allow consumers to narrow down large datasets, improving both network efficiency and client-side performance.

From First Principles: Why Filter?

A RESTful collection endpoint like GET /v1/tasks is intended to return a list of resources. However, as your database grows, returning 10,000 tasks when the user only wants to see their "pending" items is wasteful.

Filtering occurs at the server level. The client expresses a desire for a subset of the data via the URL, and the server translates that intent into a database query. Because query parameters are part of the URL, they remain cacheable and shareable, aligning with the principles we established in Statelessness in REST: Why Your Server Should Forget.

Implementing Status Filtering

To filter our tasks, we will use a query parameter named status. A request to GET /v1/tasks?status=completed signals to our backend that it should only return tasks where the status field matches "completed".

Worked Example: Parsing Query Strings

Most modern web frameworks make parsing query strings trivial. In a Node.js/Express environment, for example, req.query automatically converts the query string into a JavaScript object.

Here is how we update our GET /v1/tasks handler to support this:

JAVASCRIPT
// GET /v1/tasks?status=pending
app.get(CE9178">'/v1/tasks', (req, res) => {
  const { status } = req.query;
  
  // Start with the full list of tasks
  let filteredTasks = tasks;

  // If the CE9178">'status' parameter is provided, filter the collection
  if (status) {
    filteredTasks = tasks.filter(task => task.status === status);
  }

  // Return the standard response envelope
  res.status(200).json({
    data: filteredTasks,
    metadata: { count: filteredTasks.length }
  });
});

In this example, if the client calls GET /v1/tasks without parameters, status is undefined, the if block is skipped, and all tasks are returned. If they provide ?status=pending, we perform the filter operation before sending the JSON response.

Hands-on Exercise

Modify your current GET /v1/tasks endpoint to support an additional filter: priority.

  1. Allow the user to request GET /v1/tasks?priority=high.
  2. Ensure your code handles cases where both status and priority are provided (e.g., ?status=pending&priority=high).
  3. Hint: You can chain .filter() calls or use a single filter function that checks both conditions.

Common Pitfalls

  • Case Sensitivity: Users might type ?status=COMPLETED or ?status=completed. Always normalize your input (e.g., status.toLowerCase()) before comparing it against your data.
  • Assuming Data Types: Query parameters always arrive as strings. If you were filtering by a numeric ID, you would need to convert the string to an integer (parseInt()) before performing the filter.
  • Over-Filtering: Don't allow arbitrary filtering on every single field in your database. Only expose filters for fields that have indexes and are genuinely useful to the client. Unrestricted filtering can lead to "denial of service" scenarios where expensive database queries are triggered by malicious or poorly written client requests.

FAQ

Q: Should I use path parameters or query parameters for filtering? A: Use path parameters for identifying specific resources (e.g., /tasks/123). Use query parameters for filtering a collection (e.g., /tasks?status=completed).

Q: What if the client provides an unknown filter parameter? A: A robust API should either ignore unknown parameters or return a 400 Bad Request if you want to strictly enforce a schema. Ignoring them is the most common approach for public APIs.

Q: Is filtering the same as searching? A: Filtering is usually exact matching (e.g., status=completed), while searching typically involves partial string matches or complex logic (e.g., q=meeting). We will cover searching in a future lesson.

Recap

In this lesson, we learned that filtering is a critical tool for managing collection sizes and reducing payload bloat. By parsing query parameters, we provide clients with a flexible way to request exactly the data they need. We implemented a basic status filter and discussed the importance of input normalization and performance.

Up next: Sorting Collections — Implementing a sort parameter and handling ascending/descending logic.

Similar Posts