Browse all documentation

Sorting and pagination

Order results by one or more fields, read them a page at a time, skip the total count, and list distinct values.

Lists come back one page at a time, in the order you choose. Examples use the example schema.

Sort

Newest posts first, then by title:

Go
page, err := local.List(ctx, "posts", ridu.ListOptions{
  Sort: []query.Sort{
    query.Desc("publishedAt"),
    query.Asc("title"),
  },
  Actor: user,
})
TypeScript
const page = await ridu.list('posts', {
  sort: ['-publishedAt', 'title']
});
REST
curl -G "$RIDU/api/collections/posts" \
  -H "Authorization: Session $TOKEN" \
  --data-urlencode 'sort=-publishedAt' \
  --data-urlencode 'sort=title'

Sort terms apply in order: the second breaks ties in the first. A - prefix sorts descending. Ridu adds id as the final tie-breaker, so equal values keep a stable order from page to page.

What you can sort by

Sort by a field that holds one value, at the top level or inside a group, such as seo.title. A localized field sorts by its value in the request’s locale. These can’t be sorted, because a document has no single value to order by:

  • arrays and blocks, and fields inside them;
  • lists, multiple selects, has-many relationships and uploads, and polymorphic relationships;
  • JSON and plugin fields, such as rich text;
  • groups themselves, and fields inside a localized group.

Ridu refuses these before reading, with an unsupported_path issue. REST accepts at most 16 sort fields.

Paginate

The second page of ten posts:

Go
page, err := local.List(ctx, "posts", ridu.ListOptions{
  Page:  2,
  Limit: 10,
  Actor: user,
})
TypeScript
const page = await ridu.list('posts', { page: 2, limit: 10 });
REST
curl -G "$RIDU/api/collections/posts" \
  -H "Authorization: Session $TOKEN" \
  --data-urlencode 'page=2' \
  --data-urlencode 'limit=10'
Result: pagination
{
  "page": 2,
  "limit": 10,
  "totalDocs": 34,
  "totalPages": 4,
  "hasNextPage": true,
  "hasPrevPage": true
}

Pages start at 1. Over REST and the SDK, limit defaults to 10 and can be at most 100. In Go, page.Total points to the total and page.HasNextPage says whether another page follows.

Skip the total count

Counting every match costs more as a collection grows. For “Load more” buttons, infinite scroll and feeds, you only need to know whether another page exists:

Go
page, err := local.List(ctx, "posts", ridu.ListOptions{
  Page:      2,
  Limit:     10,
  SkipTotal: true,
  Actor:     user,
})
// page.Total is nil; page.HasNextPage is exact.
TypeScript
const page = await ridu.list('posts', {
  page: 2,
  limit: 10,
  pagination: false
});

if (page.pagination.hasNextPage) showLoadMore();
REST
curl -G "$RIDU/api/collections/posts" \
  -H "Authorization: Session $TOKEN" \
  --data-urlencode 'page=2' \
  --data-urlencode 'limit=10' \
  --data-urlencode 'pagination=false'
Result: pagination
{ "page": 2, "limit": 10, "hasNextPage": true, "hasPrevPage": true }

Ridu reads one document past the page to set hasNextPage, and leaves out totalDocs and totalPages instead of estimating them. In TypeScript, a literal pagination: false removes the totals from the result’s type too.

To count matches without listing them, see Count.

Distinct values

List each different value of one field, such as every status in use. This is available from Go only:

Go
statuses, err := local.Distinct(ctx, "posts", ridu.DistinctOptions{
  Field: query.Field("status"),
  Where: query.Exists("publishedAt", true),
  Actor: user,
})
// statuses.Values holds each value once, in ascending order.

Distinct works on a direct field that holds one value, a single relationship or upload, or id, and pages like a list. A field with a read rule is refused, because the rule may differ from one document to the next.