Pagination
@modularityjs/http-pagination parses offset pagination from the query string and wraps results in a standard envelope.
@Page()
@Page() injects a PageRequest — the parsed, clamped pagination inputs:
typescript
import { Page, paginate } from '@modularityjs/http-pagination';
import type { PageRequest } from '@modularityjs/http-pagination';
@Controller('/users')
class UsersController {
constructor(@Inject(UserService) private readonly users: UserService) {}
@Get()
async list(@Page() page: PageRequest) {
const [rows, total] = await this.users.findPage(page.offset, page.perPage);
return paginate(rows, total, page);
}
}GET /users?page=3&perPage=10 yields { page: 3, perPage: 10, offset: 20 }. The envelope:
json
{
"data": ["..."],
"meta": { "page": 3, "perPage": 10, "total": 42, "totalPages": 5 }
}Behavior
- Missing parameters fall back to
defaultPerPage/ page 1. - Garbage values (
?page=abc,?page=0, non-integers) are a client error —ValidationException, HTTP 422. - A
perPageabovemaxPerPageis clamped, not rejected, so raising the server cap never breaks stored client links.
Configuration
typescript
HttpPaginationModule.forRoot({
defaultPerPage: 25, // applied when the request has no per-page param
maxPerPage: 100, // hard cap; larger requests are clamped
pageParam: 'page', // query-string names
perPageParam: 'perPage',
});Composing with serialization
paginate is envelope-only — combine it with http-serialization's manual escape hatch to shape the items:
typescript
return paginate(
users.map((user) => serializeResource(UserResource, user, { request })),
total,
page,
);