Careeroza
Offset Pagination
page and limit
Never return a whole table. Clients ask for one page: ?page=2&limit=20. The server uses skip = (page - 1) * limit and take = limit, and returns metadata.
Example
The math, plus the meta object the client needs to draw pagination buttons:
function pageMeta(page: number, limit: number, total: number) {
const totalPages = Math.max(1, Math.ceil(total / limit));
return {
skip: (page - 1) * limit,
take: limit,
meta: { page, limit, total, totalPages, hasNext: page < totalPages, hasPrev: page > 1 },
};
}
console.log(pageMeta(1, 20, 137));
console.log(pageMeta(7, 20, 137));
console.log(pageMeta(1, 20, 0));Output
{
skip: 0,
take: 20,
meta: {
page: 1,
limit: 20,
total: 137,
totalPages: 7,
hasNext: true,
hasPrev: false
}
}
{
skip: 120,
take: 20,
meta: {
page: 7,
limit: 20,
total: 137,
totalPages: 7,
hasNext: false,
hasPrev: true
}
}
{
skip: 0,
take: 20,
meta: {
page: 1,
limit: 20,
total: 0,
totalPages: 1,
hasNext: false,
hasPrev: false
}
}A Reusable Query DTO
src/common/dto/pagination-query.dto.ts
import { Type } from 'class-transformer';
import { IsInt, IsOptional, Max, Min } from 'class-validator';
export class PaginationQueryDto {
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
page: number = 1;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
@Max(100) // protect the database from ?limit=1000000
limit: number = 20;
}Note: Query strings are always strings. @Type(() => Number) plus transform: true on the ValidationPipe converts them before validation.