14 Filtering Sorting
title: Filtering and Sorting in Node.js REST APIs weight: 24 date: 2026-06-28 lastmod: 2026-06-28 description: Implement advanced filtering and sorting in Node.js REST APIs with dynamic query building, operator support (gte, lte, in), text search, and field selection. tags: [api-development, nodejs]
Implementing filtering and sorting in Node.js REST APIs involves building dynamic database queries from query parameters, supporting operators like gte, lte, in, and text search, while whitelisting allowed fields for security.
```mermaid
flowchart TD
A[Query Parameters] --> B[Filter Builder]
B --> C{Field Whitelisted?}
C -->|Yes| D[Build Filter Object]
C -->|No| E[Reject Parameter]
D --> F[Convert Operators]
F --> G[gte -> $gte]
F --> H[in -> $in]
D --> I[Execute Query]
style A fill:#e1f5fe
style B fill:#fff9c4
style D fill:#c8e6c9
style E fill:#ffcdd2
Parse query parameters into MongoDB or SQL filter objects. Convert parameter names with operators (price_gte becomes {price: {$gte: value}}). Whitelist allowed filterable and sortable fields. Reject unknown fields with a 400 error.
Think of filtering like a search engine advanced search. Users enter criteria, and the system dynamically builds a query. The whitelist ensures users can only search on appropriate fields, preventing slow queries on unindexed columns.
Example: Advanced Filter Builder
class APIFeatures {
constructor(query, queryString) {
this.query = query;
this.queryString = queryString;
}
filter(allowedFields = []) {
const queryObj = { ...this.queryString };
const excludedFields = ['page', 'sort', 'limit', 'fields'];
excludedFields.forEach(el => delete queryObj[el]);
// Convert operators (gte, gt, lte, lt, in)
let queryStr = JSON.stringify(queryObj);
queryStr = queryStr.replace(/\b(gte|gt|lte|lt|in|ne)\b/g, match => `$${match}`);
// Parse filter fields
const filters = JSON.parse(queryStr);
// Whitelist check
if (allowedFields.length > 0) {
for (const key of Object.keys(filters)) {
const field = key.replace(/\.(gte|gt|lte|lt|in|ne)$/, '');
if (!allowedFields.includes(field)) {
delete filters[key];
}
}
}
this.query = this.query.find(filters);
return this;
}
sort(allowedFields = []) {
if (this.queryString.sort) {
const sortBy = this.queryString.sort.split(',').join(' ');
// Whitelist check
if (allowedFields.length > 0) {
const fields = this.queryString.sort.split(',');
const valid = fields.every(f => {
const field = f.replace(/^-/, '');
return allowedFields.includes(field);
});
if (!valid) {
throw new Error('Invalid sort field');
}
}
this.query = this.query.sort(sortBy);
} else {
this.query = this.query.sort('-createdAt');
}
return this;
}
paginate() {
const page = parseInt(this.queryString.page) || 1;
const limit = parseInt(this.queryString.limit) || 10;
const skip = (page - 1) * limit;
this.query = this.query.skip(skip).limit(limit);
return this;
}
fieldLimit() {
if (this.queryString.fields) {
const fields = this.queryString.fields.split(',').join(' ');
this.query = this.query.select(fields);
} else {
this.query = this.query.select('-__v');
}
return this;
}
}
// Usage
app.get('/api/products', async (req, res, next) => {
try {
const filterableFields = ['name', 'price', 'category', 'inStock', 'createdAt'];
const sortableFields = ['name', 'price', 'createdAt', 'rating'];
const features = new APIFeatures(Product.find(), req.query)
.filter(filterableFields)
.sort(sortableFields)
.fieldLimit()
.paginate();
const products = await features.query;
res.json({ status: 'success', data: products });
} catch (error) {
next(error);
}
});
Example: Text Search Implementation
// MongoDB text index
productSchema.index({ name: 'text', description: 'text' });
// Search endpoint
app.get('/api/products/search', async (req, res, next) => {
try {
const { q } = req.query;
if (!q || q.length < 2) {
return res.status(400).json({ error: 'Search query must be at least 2 characters' });
}
const products = await Product.find(
{ $text: { $search: q } },
{ score: { $meta: 'textScore' } }
)
.sort({ score: { $meta: 'textScore' } })
.limit(20);
res.json({ status: 'success', data: products });
} catch (error) {
next(error);
}
});
Example: Prisma Filtering (SQL)
// Prisma filtering
app.get('/api/products', async (req, res, next) => {
try {
const { category, minPrice, maxPrice, inStock, q } = req.query;
const where = {};
if (category) where.category = category;
if (minPrice || maxPrice) {
where.price = {};
if (minPrice) where.price.gte = parseFloat(minPrice);
if (maxPrice) where.price.lte = parseFloat(maxPrice);
}
if (inStock !== undefined) where.inStock = inStock === 'true';
if (q) {
where.OR = [
{ name: { contains: q, mode: 'insensitive' } },
{ description: { contains: q, mode: 'insensitive' } }
];
}
const products = await prisma.product.findMany({
where,
orderBy: { createdAt: 'desc' },
skip: (page - 1) * limit,
take: limit
});
res.json({ status: 'success', data: products });
} catch (error) {
next(error);
}
});
Common Mistakes
- Allowing filtering on all fields without validation — Users could filter on internal fields or fields without indexes, causing slow queries. Whitelist filterable fields.
- Not escaping special characters in text search — Search queries with regex special characters (. * + ?) can cause errors or slow queries. Escape them before building the query.
- Case-sensitive sorting by default — Database default sorting is often case-sensitive. Use case-insensitive collation or LOWER() for string fields.
- Accepting negative or unlimited pagination — Prevent negative page numbers and set a maximum limit (e.g., 100) to avoid performance issues.
- Not providing a default sort order — Without a default sort, the order of results is unpredictable. Always provide a meaningful default.
Practice Questions
- How do you convert query parameter operators like price_gte to database queries?
- Why should you whitelist filterable and sortable fields?
- How do you implement text search with MongoDB?
- What is the purpose of field limiting (sparse fieldsets)?
- Challenge: Build a comprehensive filtering, sorting, and search system for an e-commerce API. Support equality filters, range filters, text search, pagination, multi-field sorting, and field selection. Include whitelist validation for all parameters.
FAQ
Mini Project
Build a filtering and sorting middleware for an Express API. The middleware should parse query parameters, build filter objects with operator support, whitelist fields, handle text search, and return validation errors for invalid parameters. Test with a products endpoint.
What's Next
Now learn about pagination implementation in Building REST APIs with Node.js.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro