How to Implement Search, Sort, and Filter in Your Node.js + Express REST API
Delivering the right data at the right time is one of the most important responsibilities of a well-designed API. Users expect fast, accurate, and flexible querying — and that means your backend needs solid search, sort, and filter capabilities.
In this post, I’ll show you how to implement all three in a clean and scalable way using Node.js, Express, and Prisma ORM.
Why Search, Sort, and Filter Matter
A typical client request to your API includes query parameters such as:
search→ find records containing certain textcompleted→ filter by boolean-like valuesorderBy→ return data sorted by specific fields
Your server is responsible for parsing these parameters and translating them into where and orderBy conditions that your ORM can use to query the database.
In this tutorial, we’ll use Prisma ORM, but the same principles apply regardless of your data layer.
Step 1: Updating the Repository Interfaces
Before writing any logic, we start by updating the repository interface to define what our API can accept.
We add support for:
search?: stringcompleted?: booleanorderBy?: { createdAt?: "asc" | "desc"; dueDate?: "asc" | "desc" }
Why limit fields?
When your app grows, indexing becomes critical. Allowing users to search or sort on too many fields leads to index bloat and slower write operations.
That’s why in this example we limit search and sorting to a small, intentional set of fields.
🚀 Complete JavaScript Guide (Beginner + Advanced)
🚀 NodeJS – The Complete Guide (MVC, REST APIs, GraphQL, Deno)
Step 2: Implementing Search
For this project, we only search by task name.
Could we also search descriptions?
Yes — but if your project has a large dataset, description search would require:
- A full-text index, and
- Different Prisma syntax for full-text queries
If you need more advanced search capabilities, you should look into dedicated search engines like Algolia.
Step 3: Implementing Filter Logic
Filtering by completion is more complex than it looks.
Instead of a simple boolean field (isCompleted: true/false), this system uses a completedOn date. That means:
- If the task is completed →
completedOncontains a date - If not →
completedOnisnull
So filtering works like this:
completed=true→ return tasks wherecompletedOnis not nullcompleted=false→ return tasks wherecompletedOnis null- filter omitted → return all tasks
This kind of nuance appears frequently in real-world APIs, so it’s important to handle it correctly.
Step 4: Implementing Sorting
Sorting is passed as a single query parameter in the format:
createdAt-desc
The API splits this into:
- the field (
createdAtordueDate) - the direction (
ascordesc)
We validate both so users can’t sort by arbitrary fields.
The final result maps directly to Prisma’s orderBy object structure.
Step 5: Parsing Query Parameters
To keep your controllers clean and organized, it’s best to introduce a helper function:
parseTaskQueryParameters(req)
This function:
- Reads raw query values
- Validates them
- Converts them into the structured format your repository layer expects
Doing this in a centralized utility:
- avoids duplicated logic
- makes future changes easy
- keeps your controllers readable
Step 6: Updating the Controller
With the parsing logic in place, the controller becomes extremely simple:
- Extract query parameters
- Pass them to the repository
- Return the results
Clean architecture at work.
Step 7: Testing Your API
Example test queries:
/tasks?search=task/tasks?search=task&completed=false/tasks?search=task&completed=false&orderBy=createdAt-desc
The results:
- match the search term
- include only completed or incomplete tasks
- are sorted by the field and direction you specify
Everything works exactly as expected.