How to add pagination
Tango ships with two paginators:
OffsetPaginatorCursorPaginator
Most applications should start with OffsetPaginator because it is already wired into ModelViewSet and GenericAPIView.
Offset pagination
OffsetPaginator is the default paginator used by the built-in list behavior.
It reads:
limitoffsetpage
Then it returns a response envelope shaped like:
{
"count": 100,
"next": "?limit=20&offset=20",
"previous": null,
"results": []
}With ModelViewSet or GenericAPIView
You do not need to instantiate the paginator yourself when you use the built-in list behavior. Those classes already create an OffsetPaginator, parse the request, apply it to the QuerySet, and serialize the response.
What you do need, regardless of whether the paginator is created manually or by a built-in view, is stable ordering.
Declare orderingFields in the resource config and give the underlying manager or query code a deterministic default order.
Cursor pagination
CursorPaginator is intended for cases where forward traversal has to remain stable as data changes.
It reads:
limitcursorordering
The cursor token is opaque to the client. Internally it is a base64-encoded JSON payload with:
- version
- field
- direction
- value
Cursor pagination is useful when you need stable forward navigation over changing data, but it requires more care:
- pick a cursor field with stable ordering semantics
- avoid exposing arbitrary ordering when the cursor depends on one field
- document that clients should follow
nextandpreviouslinks instead of constructing cursors by hand
A simple manual example
If you want to use a paginator outside the built-in views:
import { TangoQueryParams } from '@danceroutine/tango-core';
import { OffsetPaginator } from '@danceroutine/tango-resources';
const paginator = new OffsetPaginator(PostModel.objects.query());
const params = TangoQueryParams.fromRequest(request);
paginator.parse(params);
const qs = paginator.apply(PostModel.objects.query().orderBy('-createdAt'));
const [page, totalCount] = await Promise.all([qs.fetch(PostReadSchema), PostModel.objects.query().count()]);
return paginator.toResponse(page.results, { totalCount });Common mistakes
Avoid these:
- paginating without ordering
- exposing ordering on fields that are not safe or indexed
- mixing page-based and cursor-based conventions in one endpoint without documenting it clearly