Pagination
Pagination lets you iterate through large sets returned by a query.
This guide covers default pagination, customizing page size, and accessing paginated results within FQL queries.
Default pagination
Fauna automatically paginates result Sets with 16 or more elements.
When a query returns paginated results, Fauna materializes a subset of the
Set with an after
pagination cursor:
// Uses the `Product` collection's `sortedByPriceLowToHigh()` index to
// return all `Product` collection documents.
// The collection contains more than 16 documents.
Product.sortedByPriceLowToHigh()
{
// The result Set contains 16 elements.
data: [
{
id: "555",
coll: Product,
ts: Time("2099-07-30T15:57:03.730Z"),
name: "single lime",
description: "Conventional, 1 ct",
price: 35,
stock: 1000,
category: Category("789")
},
{
id: "888",
coll: Product,
ts: Time("2099-07-30T15:57:03.730Z"),
name: "cilantro",
description: "Organic, 1 bunch",
price: 149,
stock: 100,
category: Category("789")
},
...
],
// Use the `after` cursor to get the next page of results.
after: "hdW..."
}
Customize page size
Reference: set.pageSize() |
---|
Use set.pageSize()
to change the
maximum number of elements per page:
// Calls `pageSize()` with a size of `2`.
Product.sortedByPriceLowToHigh().pageSize(2)
{
// The result Set contains two elements or fewer.
data: [
{
id: "555",
coll: Product,
ts: Time("2099-07-30T15:57:03.730Z"),
name: "single lime",
description: "Conventional, 1 ct",
price: 35,
stock: 1000,
category: Category("789")
},
{
id: "888",
coll: Product,
ts: Time("2099-07-30T15:57:03.730Z"),
name: "cilantro",
description: "Organic, 1 bunch",
price: 149,
stock: 100,
category: Category("789")
}
],
after: "hdaExad..."
}
pageSize()
should typically be the last method call in an FQL expression.
pageSize()
only affects the rendering of a Set, not subsequent operations.
Methods chained to pageSize()
access the entire calling Set, not a page of
results.
Iterate through pages
Reference: Set.paginate() |
---|
To iterate through paginated results, pass the after
cursor to
Set.paginate()
:
Set.paginate("hdW...")
Example implementation
The following example shows how you can iterate through paginated results using the JavaScript driver:
import { Client, fql } from "fauna";
const client = new Client({
secret: '<FAUNA_SECRET>'
});
// Defines a function that accepts a Fauna `after` cursor as
// an optional argument.
async function getProducts(afterCursor) {
// Initial FQL query.
const query = fql`Product.sortedByPriceLowToHigh().pageSize(2)`;
const response = await client.query(
// If an `after` cursor is provided, use `Set.paginate()`
// to get the next page of results.
// Otherwise, use the initial FQL query.
afterCursor ? fql`Set.paginate(${afterCursor})` : query
);
const data = response.data.data;
const nextCursor = response.data.after;
// Print the results and the after cursor.
console.log("Data:", data);
console.log("Next cursor:", nextCursor);
return { data, nextCursor };
}
// Defines a function to loop through paginated results.
async function getAllProducts() {
let afterCursor;
do {
const { data, nextCursor } = await getProducts(afterCursor);
afterCursor = nextCursor;
} while (afterCursor);
}
// Call the function to loop through the results.
getAllProducts();
Access pages and cursors within a query
Reference: set.paginate() |
---|
If you need to access an after
cursor or paginated results within an FQL
query, use set.paginate()
:
Product.sortedByPriceLowToHigh().pageSize(2).paginate()
For example, you can use paginate()
to return the after
cursor for use
as a URL in a client application.
Alternatively, you can use paginate()
to iteratively update a large Set of
collection documents over several transactions. For an example, see the
paginate()
reference docs.
Considerations for paginate()
paginate()
accepts an optional argument to control page size. In most cases,
you should not use paginate()
in place of pageSize()
.
The following table outlines differences between
set.pageSize()
and set.paginate()
:
Difference | set.pageSize() |
set.paginate() |
---|---|---|
Use case |
Use in most cases. |
Use when needing to access an 'after' cursor or paginated results within an FQL query. |
Return type |
Returns a set. |
Returns an object. |
Loading strategy |
Lazy loading. Only fetches results as needed. |
Eager loading. Fetches results instantly, even if the results aren’t returned or used. |
Client driver methods |
Compatible with driver pagination methods. |
Incompatible with driver pagination methods. |
Projection |
Supports projections. |
Doesn’t support projections. |
Set instance methods |
Supports set instance methods. |
Doesn’t support set instance methods. |
Cursor state and expiration
If a paginated Set contains documents, the after
cursor fetches historical
snapshots of the documents at the time of the original query.
You can control the retention of document snapshots using the collection
schema’s history_days
field. An after
cursor is valid for history_days
plus 15 minutes. If history_days
is 0
or unset, the cursor is valid for 15
minutes.
If a document’s snapshot is no longer available, a NullDoc is returned instead:
{
data: [
{
id: "393605620096303168",
coll: Product,
ts: Time("2099-03-28T12:53:40.750Z"),
name: "limes",
...
},
Product("401942927818883138") /* not found */
],
after: "hdWCxmd..."
}
See Document history |
---|
Cursor handling and best practices
When working with after
cursors, keep the following in mind:
-
Encoding:
after
cursors can contain special characters, such as+
.If you’re using an after cursor in a URL or as a query parameter, make sure to URL-encode the cursor value. This ensures that the cursor is properly transmitted and interpreted.
Generally, we don’t recommend you use
after
cursors for user-facing pagination. See UI pagination. -
Cursor integrity: You shouldn’t directly change or manipulate
after
cursor values. The cursor is a hash that contains all information required to get the next page in the Set.Any modification to the cursor could result in unexpected behavior or errors when retrieving the next page.
Paginate in reverse
Paginated queries don’t include a before
cursor. Instead, you can use a range
search and document IDs or other unique field values to paginate in reverse. For
example:
-
Run an initial paginated query:
Product.all().pageSize(2)
{ data: [ { id: "111", coll: Product, ts: Time("2099-08-16T14:00:59.075Z"), name: "cups", description: "Translucent 9 Oz, 100 ct", price: 698, stock: 100, category: Category("123") }, { id: "222", coll: Product, ts: Time("2099-08-16T14:00:59.075Z"), name: "donkey pinata", description: "Original Classic Donkey Pinata", price: 2499, stock: 50, category: Category("123") } ], after: "hdW..." }
-
Page forward until you find the document you want to start reversing from:
Set.paginate("hdW...")
Copy the ID of the document:
{ data: [ { id: "333", coll: Product, ts: Time("2099-08-16T14:00:59.075Z"), name: "pizza", description: "Frozen Cheese", price: 499, stock: 100, category: Category("456") }, { // Begin reverse pagination from this doc ID. id: "444", coll: Product, ts: Time("2099-08-16T14:00:59.075Z"), name: "avocados", description: "Conventional Hass, 4ct bag", price: 399, stock: 1000, category: Category("789") } ], after: "hdW..." }
-
To reverse paginate, run the original query with:
-
A range search with a
to
argument containing the previous document ID. -
set.reverse()
: Append this to the query. -
set.pageSize()
: If used, place it afterset.reverse()
.
// "444" is the ID of the document to reverse from. Product.all({ to: "444" }).reverse().pageSize(2)
{ data: [ { // The results of the previous query are reversed. id: "444", coll: Product, ts: Time("2099-08-16T14:00:59.075Z"), name: "avocados", description: "Conventional Hass, 4ct bag", price: 399, stock: 1000, category: Category("789") }, { id: "333", coll: Product, ts: Time("2099-08-16T14:00:59.075Z"), name: "pizza", description: "Frozen Cheese", price: 499, stock: 100, category: Category("456") } ], after: "hdW..." }
To get historical snapshots of documents at the time of the original query, use an
at
expression:// Time of the original query. let originalQueryTime = Time.fromString("2099-08-16T14:30:00.000Z") at (originalQueryTime) { // "444" is the ID of the document to reverse from. Product.all({ to: "444" }).reverse().pageSize(2) }
-
-
Repeat the previous step to continue paginating in reverse:
Product.all({ to: "333" }).reverse().pageSize(2)
UI pagination
Fauna’s pagination functionality is designed to help client apps consume large result Sets at a controlled pace. It isn’t intended for use in user-facing pagination.
To implement user-facing pagination in your app, we recommend you use range searches instead. Range searches give you precise control over returned data and consistent results.
Build UI pagination with ranged searches
For example, an e-commerce application must present users with a paginated list of products. Products are sorted by ascending price with up to five products per page.
To implement UI pagination using ranged searches:
-
Define an index in the
Product
collection schema:collection Product { ... index inOrderOfPrice { // `values` are document fields for sorting and range searches. // In this example, you sort and filter index results by their // ascending `price` field value. Include the document `id` // to handle cases where multiple products have the same price. values [ .price, .id ] } }
Submit the updated schema to Fauna using the Fauna Dashboard or the Fauna CLI's
fauna schema push
command. -
To get the first page of results, the app can run the following FQL query:
// Gets the first 5 products, starting with a price of `0`. Product.inOrderOfPrice({ from: [0] })! .take(5) { id, name, price }
{ data: [ { id: "555", name: "single lime", price: 35 }, { id: "888", name: "cilantro", price: 149 }, { id: "777", name: "limes", price: 299 }, { id: "666", name: "organic limes", price: 349 }, { id: "444", name: "avocados", price: 399 } ] }
-
To fetch the next page, use the
price
andid
of the last item from the previous result Set. Because range searches are inclusive, fetch six products and drop the first one, which is a duplicate from the last page.// Gets the next 6 products, starting with the `price` (399) and // document `id` of the last page's Set. Drop the first element. Product.inOrderOfPrice({ from: [399, "444"] })! .take(6) .drop(1) { id, name, price }
{ data: [ { id: "333", name: "pizza", price: 499 }, { id: "111", name: "cups", price: 698 }, { id: "999", name: "taco pinata", price: 2399 }, { id: "222", name: "donkey pinata", price: 2499 }, { id: "123", name: "gorilla pinata", price: 2599 } ] }
Is this article helpful?
Tell Fauna how the article can be improved:
Visit Fauna's forums
or email docs@fauna.com
Thank you for your feedback!