Skip to content

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 perPage above maxPerPage is 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,
);