Imagine you're building a music database application that needs to display lists of musicians, bands, and instruments. When you have thousands of records, sending all the data at once can be overwhelming for both the server and the client. We need to implement pagination to break the data into manageable chunks.
The core challenges we need to solve are:
Let's break this down into manageable steps:
// GET /musicians endpoint
app.get('/musicians', async (req, res, next) => {
// Parse query parameters with defaults
let { page = 1, size = 5 } = req.query;
// Convert to numbers since query params are strings
page = Number(page);
size = Number(size);
// Calculate limit and offset
const pagination = {};
// Special case: page=0 means return all records
if (page !== 0) {
pagination.limit = size;
pagination.offset = size * (page - 1);
}
// Query with pagination
const musicians = await Musician.findAll({
order: [['lastName'], ['firstName']],
attributes: ['id', 'firstName', 'lastName'],
include: [{
model: Band,
attributes: ['id', 'name']
}],
...pagination // Spread the pagination options
});
res.json(musicians);
});
// Pagination middleware
const paginationMiddleware = (req, res, next) => {
let { page = 1, size = 5 } = req.query;
page = Number(page);
size = Number(size);
const pagination = {};
if (page !== 0) {
pagination.limit = size;
pagination.offset = size * (page - 1);
}
// Attach pagination to request object
req.pagination = pagination;
next();
};
// Use middleware in routes
app.get('/musicians', paginationMiddleware, async (req, res, next) => {
const musicians = await Musician.findAll({
...req.pagination,
order: [['lastName'], ['firstName']],
attributes: ['id', 'firstName', 'lastName'],
include: [{
model: Band,
attributes: ['id', 'name']
}]
});
res.json(musicians);
});
When making requests to your API, clients can include query parameters to control pagination:
The formula offset = size * (page - 1) works like this:
Think of pagination like:
There are several common ways to implement pagination in APIs:
Uses page numbers and page sizes. Good for:
Uses a pointer to the last seen record. Better for:
When implementing pagination, remember to:
When working with pagination, keep in mind: