Pagination
Overview
Pagination is implemented in the relay style. Reference documentation can be found here. Both forward and backward pagination are supported:
- the arguments for forward pagination are
first
(specifing the maximum number of items to return) andafter
(which is an opaque cursor, typically pointing to the last item in the previous page) - the arguments for backward pagination are
last
(specifying the maximum number of items to return) andbefore
(which is an opaque cursor, typically pointing to the first item in the next page) - a
skip
argument is available for both forward and backward pagination and specifies a number of records to be skipped (from the given cursor) before retrieving the requested number of records.
Note
The values for first
, last
, and skip
must be positive integers.
Warning
Pagination does not work with grouping (groupBy
and having
arguments).
Information about a page is returned in the pageInfo
node. This contains the following fields:
Field | Description |
---|---|
hasNextPage |
Indicates whether there are more records to fetch after the current page. |
hasPreviousPage |
Indicates whether there are more records to fetch before the current page. |
startCursor |
An opaque cursor pointing to the first record in the current page. |
endCursor |
An opaque cursor pointing to the last record in the current page. |
When forward pagination is performed, the hasNextPage
field is true
if there are more records to fetch after the current page. When backward pagination is performed, the hasNextPage
field is true
if there are more records to fetch before the current page.
Similarly, when forward pagination is performed, the hasPreviousPage
field is true
if there are more records to fetch before the current page. When backward pagination is performed, the hasPreviousPage
field is always false
.
Understanding pagination
In the following example, we fetch the first 5 associtates. This being the first page, there is no argument for the after
parameter. Passing a null
value for the after
parameter is equivalent to not passing it at all.
query read($cid : Int!)
{
useCompany(no: $cid)
{
associate(first: 5)
{
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
associateNo
name
}
}
}
}
{
"data": {
"useCompany": {
"associate": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "MQ==",
"endCursor": "NQ=="
},
"items": [
{
"associateNo": 1,
"name": "Et Cetera Solutions"
},
{
"associateNo": 2,
"name": "Rock And Random"
},
{
"associateNo": 3,
"name": "Handy Help AS"
},
{
"associateNo": 4,
"name": "EasyWay Crafting"
},
{
"associateNo": 5,
"name": "Zen Services AS"
}
]
}
}
}
}
For a following page, we need to take the value of the endCursor
return from a query and supply it as the argument to after
. This is shown in the following example, where "NQ=="
from the query above was supplied for the after
parameter:
query read($cid : Int!)
{
useCompany(no: $cid)
{
associate(first: 5, after: "NQ==")
{
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
associateNo
name
}
}
}
}
{
"data": {
"useCompany": {
"associate": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": true,
"startCursor": "Ng==",
"endCursor": "MTA="
},
"items": [
{
"associateNo": 6,
"name": "Smart Vita AS"
},
{
"associateNo": 7,
"name": "Full Force Services"
},
{
"associateNo": 8,
"name": "HomeRun Auto AS"
},
{
"associateNo": 9,
"name": "Trade Kraft AS"
},
{
"associateNo": 10,
"name": "Nodic Cool Sports AS"
}
]
}
}
}
}
Requests with backwards pagination are performed similarly, exept that last
and before
are used instead of first
and after
. In the following example, the value of before
is taken from the value of startCursor
returned by the previous query that returned the second page of associates (with five records per page). As a result, the data returned from this new query is actually the first page of associates.
query read($cid : Int!)
{
useCompany(no: $cid)
{
associate(last: 5, before: "Ng==")
{
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
associateNo
name
}
}
}
}
{
"data": {
"useCompany": {
"associate": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "MQ==",
"endCursor": "NQ=="
},
"items": [
{
"associateNo": 1,
"name": "Et Cetera Solutions"
},
{
"associateNo": 2,
"name": "Rock And Random"
},
{
"associateNo": 3,
"name": "Handy Help AS"
},
{
"associateNo": 4,
"name": "EasyWay Crafting"
},
{
"associateNo": 5,
"name": "Zen Services AS"
}
]
}
}
}
}
Providing both forward and backward pagination arguments (first
/after
and last
/before
) is illegal and the query will fail with an error.
If pagination arguments are not supplied a default page size of 5000 records is used.
When you fetch data in pages (which is recommended for most scenarious) you can fetch the total number of objects in the table with disregard to the page that is fetched or the size of the page. This is possible with the totalCount
. This will return the number of records that match the filter (if any) but ignoring paginagion (if any).
This feature is exemplified below. We, again, fetch the first 5 associates that have a customer number greater or equal than 11000 but also request the total number of records that match this filter.
query read($cid : Int!)
{
useCompany(no: $cid)
{
associate(
first: 5,
filter: {customerNo :{_gte: 11000}})
{
totalCount
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
associateNo
customerNo
name
}
}
}
}
{
"data": {
"useCompany": {
"associate": {
"totalCount": 29,
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "MQ==",
"endCursor": "NQ=="
},
"items": [
{
"associateNo": 308,
"customerNo": 11000,
"name": "Auto Glory AS"
},
{
"associateNo": 309,
"customerNo": 11001,
"name": "Home Team Business"
},
{
"associateNo": 310,
"customerNo": 11003,
"name": "Corpus Systems"
},
{
"associateNo": 311,
"customerNo": 11004,
"name": "Dash Credit AS"
},
{
"associateNo": 312,
"customerNo": 11007,
"name": "Smart Help Services AS"
}
]
}
}
}
}
You can skip a number of records before feetching a page (with the size indicated by either the first
or the last
arguments) using the argument skip
. This is an optional argument. When present, it indicates the number of records to be skipped. Here is an example:
query read($cid : Int!)
{
useCompany(no: $cid)
{
generalLedgerAccount(skip : 5,
first: 10)
{
totalCount
items
{
accountNo
}
}
}
}
query read($cid : Int!)
{
useCompany(no: $cid)
{
generalLedgerAccount(skip : 5,
last : 10,
before: "MTE=")
{
totalCount
items
{
accountNo
}
}
}
}
Warning
Because the execution of queries for joined tables is optimized for performance, pagination does not work as described in this document. To understand the problem and the workaround see Unoptimized queries.
Pagination in depth
To understand how pagination works, we will consider the following example, where the general ledger account table contains 25 records. It could look like this:
page 1 (10 records) page 2 (10 records) page 3 (5 records)
|---------------------------------|---------------------------------|---------------------------------|
| 1000 | 1020 | ... | 1100 | 1120 | 1130 | 1140 | ... | 1240 | 1250 | 1260 | 1270 | 1280 | 1300 | 1310 |
Forward pagination
To fetch the records from the beginning, we perform a query like this:
query read($cid : Int!)
{
useCompany(no: $cid)
{
generalLedgerAccount(first : 10,
after: null)
{
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
accountNo
}
}
}
}
{
"data": {
"useCompany": {
"generalLedgerAccount": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "MQ==",
"endCursor": "MTA="
},
"items": [
{
"accountNo": 1000
},
{
"accountNo": 1020
},
...
{
"accountNo": 1100
},
{
"accountNo": 1120
}
]
}
}
}
}
This returns the first ten records, from index 1 to 10, and the startCursor
and endCursor
are pointing to the first and last elements in the set.
page 1 (10 records)
|---------------------------------|
| 1000 | 1020 | ... | 1100 | 1120 | 1130 | 1140 | ... | 1240 | 1250 | 1260 | 1270 | 1280 | 1300 | 1310 |
^ ^
| |
startCursor endCursor
To fetch the next page of 10 records, we provide the value of endCursor
as the argument for after
:
query read($cid : Int!)
{
useCompany(no: $cid)
{
generalLedgerAccount(first : 10,
after: "MTA=")
{
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
accountNo
}
}
}
}
{
"data": {
"useCompany": {
"generalLedgerAccount": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": true,
"startCursor": "MTE=",
"endCursor": "MjA="
},
"items": [
{
"accountNo": 1130
},
{
"accountNo": 1140
},
...
{
"accountNo": 1240
},
{
"accountNo": 1250
}
]
}
}
}
}
This returns the second page of ten records, from index 11 to 20, and the startCursor
and endCursor
are pointing to the first and last elements in the set.
after page 2 (10 records)
| |---------------------------------|
v
| 1000 | 1020 | ... | 1100 | 1120 | 1130 | 1140 | ... | 1240 | 1250 | 1260 | 1270 | 1280 | 1300 | 1310 |
^ ^
| |
startCursor endCursor
To fetch the next page of 10 records, we repeat the operation, again, using the endCursor
for the value of the after
argument:
query read($cid : Int!)
{
useCompany(no: $cid)
{
generalLedgerAccount(first : 10,
after: "MjA=")
{
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
accountNo
}
}
}
}
{
"data": {
"useCompany": {
"generalLedgerAccount": {
"pageInfo": {
"hasNextPage": false,
"hasPreviousPage": true,
"startCursor": "MjE=",
"endCursor": "MjU="
},
"items": [
{
"accountNo": 1260
},
{
"accountNo": 1270
},
{
"accountNo": 1280
},
{
"accountNo": 1300
},
{
"accountNo": 1310
},
]
}
}
}
}
This returns a page of only 5 records, from index 21 to 25, and the startCursor
and endCursor
are pointing to the first and last elements in the set.
after page 3 (5 records)
| |----------------------------------|
v
| 1000 | 1020 | ... | 1100 | 1120 | 1130 | 1140 | ... | 1240 | 1250 | 1260 | 1270 | 1280 | 1300 | 1310 |
^ ^
| |
startCursor endCursor
The value of the hasNextPage
field is false
because there are no more records to fetch.
Note
The value used for the after
argument should be the value of the endCursor
from the previous page (unless it’s null
, in which case it means the fetching should start from the beginning). The after
cursor identifies the position of the last record in a (previous) page. The record at the position represented by the cursor passed to the after
argument is not included in the result set. A page starts with the record following the one identified by after
.
It is also possible to skip records before fetching a page. This is done by providing the skip
argument. The following example skips the first 3 records before fetching a page of 10 records. We don’t start from the beginning, but at the end of the first page of 10 records, as exemplify below:
after skip 3 records page of 10 records
| >-------------------<|-----------------------------------------------|
v
| 1000 | 1020 | ... | 1100 | 1120 | 1130 | 1140 | 1150 | 1160 | ... | 1240 | 1250 | 1260 | 1270 | 1280 | 1300 | 1310 |
^ ^
| |
startCursor endCursor
query read($cid : Int!)
{
useCompany(no: $cid)
{
generalLedgerAccount(first : 10,
skip : 3,
after: "MTA=")
{
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
accountNo
}
}
}
}
{
"data": {
"useCompany": {
"generalLedgerAccount": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": true,
"startCursor": "MTQ=",
"endCursor": "MjM="
},
"items": [
{
"accountNo": 1160
},
{
"accountNo": 1200
},
...
{
"accountNo": 1270
},
{
"accountNo": 1280
}
]
}
}
}
}
Backward pagination
Backward pagination works similarly, except that the last
and before
arguments are used instead of first
and after
. The argument last
indicates how many records the page should contain. The argument before
represents the position of the record before which the page is located. The record at the before
position is not included in the result set.
Therefore, if in the preceding example we fetched all the records in the table, and now want to move backwards, but use the value of the endCursor
as the arguement for before
, then this last record will not be included in the returned page.
page of 10 records before
|---------------------------------------------------------------------| |
v
| 1000 | 1020 | ... | 1160 | 1200 | 1210 | 1220 | 1230 | 1240 | 1250 | 1260 | 1270 | 1280 | 1300 | 1310 |
^ ^
| |
startCursor endCursor
query read($cid : Int!)
{
useCompany(no: $cid)
{
generalLedgerAccount(last : 10,
before: "MjU=")
{
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
accountNo
}
}
}
}
{
"data": {
"useCompany": {
"generalLedgerAccount": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "MTU=",
"endCursor": "MjQ="
},
"items": [
{
"accountNo": 1200
},
{
"accountNo": 1210
},
...
{
"accountNo": 1280
},
{
"accountNo": 1300
}
]
}
}
}
}