Pagination, Filtering & Sorting
Returning thousands of database records in a single response is a common performance mistake. Pagination, filtering, and sorting make your API efficient and developer-friendly for large datasets.
1 Pagination Strategies
- Offset Pagination: Use
pageandlimitparams. Simple but can miss records on large, fast-changing datasets. - Cursor Pagination: Uses a
cursor(last seen ID) for efficient, consistent pagination on large collections. Used by Facebook, Twitter.
JavaScript — Offset Pagination (Express + Mongoose)
router.get("/", async (req, res) => {
// Parse pagination params with safe defaults
const page = Math.max(1, parseInt(req.query.page) || 1);
const limit = Math.min(100, parseInt(req.query.limit) || 20);
const skip = (page - 1) * limit;
// Build filter object from query params
const filter = {};
if (req.query.category) filter.category = req.query.category;
if (req.query.inStock) filter.inStock = req.query.inStock === "true";
// Build sort (e.g. ?sort=-price,name means price DESC, name ASC)
const sortStr = req.query.sort || "-createdAt";
const sort = {};
sortStr.split(",").forEach(field => {
if (field.startsWith("-")) sort[field.slice(1)] = -1;
else sort[field] = 1;
});
const [data, total] = await Promise.all([
Product.find(filter).sort(sort).skip(skip).limit(limit),
Product.countDocuments(filter)
]);
res.json({
success: true,
data,
meta: {
page, limit, total,
totalPages: Math.ceil(total / limit),
hasNextPage: page * limit < total,
hasPrevPage: page > 1
}
});
});
2 Query Parameter Conventions
REST — Query Parameter Patterns
# Pagination
GET /api/v1/products?page=2&limit=20
# Filtering
GET /api/v1/products?category=electronics&inStock=true
GET /api/v1/products?minPrice=1000&maxPrice=5000
# Sorting (prefix - for descending)
GET /api/v1/products?sort=-price # price DESC
GET /api/v1/products?sort=name,-createdAt # name ASC, createdAt DESC
# Field selection (sparse fieldsets)
GET /api/v1/products?fields=id,name,price
# Search
GET /api/v1/products?q=laptop
# Combined
GET /api/v1/products?category=laptops&sort=-price&page=1&limit=10
3 Code Challenge
Challenge: Implement cursor-based pagination for a
GET /messages endpoint. Use the last message's _id as the cursor, and return a nextCursor in the response metadata for the client to use in the next request.