Offset Pagination with PagedResult<T> as the Standard for HTTP List Endpoints
Status: Accepted
Date: 2026-01-01
Context
List endpoints require pagination to avoid returning unbounded result sets. Several pagination strategies were evaluated:
- Offset pagination (
page,pageSize). Simple, well-understood, supports jumping to any page. Suffers from page drift when records are added or deleted between requests. - Cursor pagination (
cursor,pageSize). No page drift, works well for infinite scroll. Cannot jump to arbitrary pages. More complex to implement. IAsyncEnumerable<T>streaming. Streams rows from the database as the consumer iterates. Genuinely useful for background processing and data export. Not appropriate for HTTP endpoints because the frontend client buffers the entire HTTP response before processing it, eliminating the streaming benefit at the HTTP boundary.
Offset pagination with an envelope response shape was chosen as the standard for HTTP endpoints. The envelope (PagedResult<T>) carries items, total count, page number, page size, and derived properties (HasNextPage, HasPreviousPage, TotalPages). This shape works cleanly with OpenAPI documentation and TypeScript client generation.
IAsyncEnumerable<T> via LiteBus’s IStreamQuery<T> and IStreamQueryHandler<T> is reserved for background processing and data export scenarios where the consumer processes items incrementally and the HTTP boundary is not involved.
Cursor pagination is the documented escalation path for high-traffic list endpoints where page drift causes observable problems. It requires a project-level ADR when adopted.
Decision
All HTTP list endpoints use offset pagination with PagedResult<T> and PaginationParameters defined in Application.Read.Contracts/Shared/. IAsyncEnumerable<T> is used only for IStreamQuery<T> handlers in background processing and export scenarios.
Consequences
Positive
- Consistent pagination shape across all list endpoints.
PagedResult<T>maps cleanly to OpenAPI and TypeScript client types.PaginationParametersenforces a maximum page size, preventing unbounded queries.
Negative
- Offset pagination has page drift for high-write endpoints. Acceptable for most use cases.
- Two database round trips per paginated query: one for the count, one for the items.
Risks
- The
TotalCountquery can be expensive on large tables. Document theSkipTotalCountoption onPaginationParametersfor infinite scroll endpoints where total count is not needed.