39 Filter & Pagination Schemes: Best Practices
39 Filter & Pagination Schemes: Best Practices
Filtering and pagination are two crucial topics in modern web application development. Lately, the need to process large amounts of data keeps growing—whether it’s product data in e-commerce, articles, users, activity logs, or other API resources. Efficient API connections, an optimal user experience, and even your back-end resources all depend on the filter and pagination scheme you put in place. That’s why it’s so important to understand the best practices and the right implementation patterns.
In this article, I’ll walk through the 39 most common filter & pagination schemes, review the best practices you need to understand, and show you code examples, simulated API responses, and even flow diagrams (using mermaid). While there are literally dozens of variations, we’ll focus on the most relevant ones you’ll often run into in the real world.
Why Are Filtering & Pagination Important?
In both REST and GraphQL applications, without pagination or filtering, an endpoint will return the entire dataset—which is clearly inefficient (and can even take down your system!). Users only want a subset of results, with specific criteria—this is exactly the role of filtering and pagination.
Benefits:
- Faster Responses: The query only fetches a subset of the data.
- Bandwidth Savings: You transfer only the data that’s actually needed.
- Better UX: Navigating the data becomes more comfortable.
- Avoiding Database Overload: Queries become lighter.
39 Filter & Pagination Schemes
The schemes below are categorized by filtering, pagination, and combinations of the two.
I. Filtering Schemes (14)
Basic Query String
Example:GET /products?category=shoes&color=black
As simple as adding parameters to the URL.Advanced Filter Operators
For example:GET /users?age[gt]=18&age[lt]=45Multiple Values (Array Query)
GET /posts?tags=tech,devops,cloudExclude Filter
GET /books?exclude_status=archivedRange (Between)
GET /sales?date_from=2024-01-01&date_to=2024-01-31Partial Match
GET /contacts?name_like=adamIN/NOT IN
GET /id_list?user_id[in]=1,2,3Nested Object Filter
GET /orders?customer.address.city=JakartaBoolean Filter
GET /items?active=trueCase Sensitive/Insensitive
GET /customers?email__iexact=gmail.comRegex Filtering
GET /files?name_regex=^fooNull/Not Null
GET /payment?receipt_number[null]=trueSorting
GET /products?sort=price,-nameAggregation Filtering
GET /teams?member_count[gt]=5
II. Pagination Schemes (13)
Offset-Limit
GET /posts?offset=0&limit=10
Classic and easy, but problematic with large or changing datasets.
Page-Size
GET /items?page=1&size=25
Cursor-based Pagination
GET /products?after=YXNkMTIz&limit=10
More reliable when data changes frequently.
Seek Method
GET /comments?last_id=2024&limit=10
Keyset Based
- Similar to seek, using a unique field (
createdAt,id, etc.).
- Similar to seek, using a unique field (
Time-based Keyset
- Pagination based on a timestamp.
Backward Pagination (previous)
- Supports navigating backward.
GET /orders?before=...
Range Pagination
- Use a range:
GET /logs?from=567&to=577
- Use a range:
Bookmarked/Custom Cursor
- Supports bookmarks to jump to a specific position.
Numbered Link Response
- The API provides numbered links: prev, next, first, last.
Infinite Scroll Support
- For mobile/SPA apps with endless scrolling.
Page Token
- Google APIs:
GET /videos?pageToken=abc123
- Google APIs:
Relational Pagination
- Pagination within a subresource.
III. Combinations & Extended Schemes (12)
Filter + Pagination
- Common:
GET /products?category=shoes&limit=10&offset=0
- Common:
Filter + Sort + Paginate
- Everything combined.
Aggregated Pagination
- While computing summary data at the same time.
Stateless Cursor
- The cursor doesn’t store any user state.
Stateful Cursor
- State specific to each user/role.
Caching-aware Pagination
- Pagination stays consistent even when cached data changes.
Pre-Fetch Pagination
- Processes 2 pages at once for a better user experience.
Chunked Pagination
- Includes a “hint” with the total number of chunks/pages.
Meta-based Pagination
- The response contains a meta object:
1{ 2 "data": [...], 3 "meta": { "pagination": { "page": 1, "total": 99 } } 4}
- The response contains a meta object:
GraphQL Connection Pattern
- edges, nodes, pageInfo.
Custom Encrypted Tokens
- Cursors in the form of an encrypted blob.
Slicing, Windowing, Partitioning
- Data is fetched per slice (window), typically in OLAP/BI APIs.
Filter & Pagination Best Practices
Let’s go over the best practices that, in my opinion, are a must-adopt:
- Combine Filtering, Sorting, and Pagination — Almost every real-world requirement needs all three at once.
- Reasonable Default Limit — Always set a default limit and a maximum limit per page (for example, max 100).
- Use Cursors for Fast-Changing Data — Cursor-based pagination is far more reliable on rapidly changing tables.
- Document Your Parameters — List the valid filter/sort fields in your API documentation, including operators/fields.
- Meta Pagination Info — Include total records/pages if performance allows, to make life easier for the frontend.
- Consistent Responses — The response schema (fields, meta, structure) must be consistent.
- Don’t Support Hidden Filters — Only filter on fields that are already queryable/indexed in the database.
- Handle Input Errors — Validate input (for example,
limitmust not exceed the max, filter fields must be valid). - Allow Multi-value Filter Combinations — Array filters and OR/AND operators are important for advanced search.
- Paginate the API Response, Not the DB Query — Get into the habit of paginating the query result, not the entire table.
Pagination Flow Diagram (Offset vs Cursor)
Let’s compare offset vs cursor:
flowchart TD
Start([Start Request]) --> CheckType{Pagination Type?}
CheckType -- Offset-Based --> OffsetDB[Query DB dengan OFFSET dan LIMIT]
CheckType -- Cursor-Based --> WhereCursor[WHERE id > last_id ORDER BY id LIMIT N]
OffsetDB --> RespOffset[Build & Send Response]
WhereCursor --> RespCursor[Build & Send Response (with next cursor)]
RespOffset --> End
RespCursor --> End
Quick Notes:
- Offset:
Simple, but slow for very large datasets (because the DB has to skip N rows). - Cursor:
More efficient when data changes (insert/delete), and a better fit for infinite scroll.
Simulated Pagination Responses
Offset-based
1{
2 "data": [{ "id": 101, "title": "Item 1" }, ...],
3 "meta": {
4 "page": 1,
5 "limit": 10,
6 "total": 99
7 }
8}Cursor-based
1{
2 "data": [{ "id": 121, "title": "Item 21" }, ...],
3 "next_cursor": "e2lkOjEyMSxjcmVhdGVkQXQ6dGltZXN0YW1w",
4 "has_next_page": true
5}Code Example: Implementing Pagination & Multi-Filter (Node.js + Express + Prisma)
1app.get("/api/users", async (req, res) => {
2 const { page = 1, size = 20, sort = "id", order = "asc", name, age_gt, age_lt } = req.query;
3 const where = {};
4 if (name) where.name = { contains: name, mode: 'insensitive' };
5 if (age_gt) where.age = { ...where.age, gt: Number(age_gt) };
6 if (age_lt) where.age = { ...where.age, lt: Number(age_lt) };
7
8 const [data, total] = await Promise.all([
9 prisma.user.findMany({
10 where,
11 skip: (page - 1) * size,
12 take: Number(size),
13 orderBy: { [sort]: order }
14 }),
15 prisma.user.count({ where })
16 ]);
17
18 return res.json({
19 data,
20 meta: {
21 page: Number(page),
22 size: Number(size),
23 total
24 }
25 });
26});Table: 39 Schemes (Summary)
| Category | Scheme (Example) | When to Use |
|---|---|---|
| Filter | Partial match, in, range | User search/filtering |
| Pagination | Offset, cursor, token | Lists, infinite scroll, large datasets |
| Combination | Filter+Sort+Paginate | Dashboards, advanced search |
| Extended | Aggregated, windowing | Analytics, OLAP, reporting |
Tips & Antipatterns
- Antipattern:
limit=1000without validation → it will hammer your DB resources. - Don’t leak internal data: Don’t allow filtering on sensitive fields, such as
hashed_password. - Use an Index on the fields most frequently used for filtering & sorting.
- Check your filter results: Empty data should still be a success (return
[], not an error).
Closing
Choosing and implementing the right filter & pagination scheme can be a major factor in your application’s scalability, performance, and ease of development. Master the 39 scheme variations above, pick the one that best fits your business context, and make sure user experience stays a priority.
Share the filter & pagination scheme you find most effective in the comments! 🚀