
Update 2021 02 13
The issue described below is fixed. See https://www.lotharschulz.info/2020/03/18/how-to-break-github-graphql-pagination/#comment-19197 for details.
~ 3 minutes read
no time? jump straight to conclusion
GraphQL is a query language for APIs. GitHubs API powers graphql with a live graphql API (as well as REST). A common use case in graphql is traversing the relationship between sets of objects known as pagination. GitHub uses a complete connection model for pagination.
This post describes an issue with GitHubs graphql pagination and how to work around it.
How to
One use case for pagination is to retrieve a list of repositories per organization. Such a graphql retrieval request can consist of a query and query variables.
Query:
query ($org: String!, $after: String, $createdat: String!, $order: String!) {
organization(login: $org) {
description
repositories(first: 2, after: $after, orderBy: {field: $createdat, direction: $order}) {
nodes {
url
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
}
totalCount
}
}
}
Query variables:
{
"org": "[your_org]",
"after": null,
"createdat": "CREATED_AT",
"order": "ASC"
}
The result includes:
...
"pageInfo": {
"endCursor": "Y3Vyc29yOnYyOpLAzgD-FL4=",
"hasNextPage": true,
"hasPreviousPage": false
},
...
The next page gets called with adapted query variables:
{
"org": "[your_org]",
"after": "Y3Vyc29yOnYyOpLAzgD-FL4=",
"createdat": "CREATED_AT",
"order": "ASC"
}
This results includes:
...
"repositories": {
"nodes": [],
"pageInfo": {
"endCursor": null,
"hasNextPage": false,
"hasPreviousPage": false
},
...
The repository request includes an order direction:
orderBy: {field: $createdat, direction: $order}
This order direction broke the pagination. Although there are more repositories on GitHub for the specific organization I tested that with, the repositories are not listed in the result payload.
The Fix
Remove the order directive to workaround the pagination issue:
orderBy: {field: $createdat, direction: $order}
Now the query looks like this:
query ($org: String!, $after: String) {
organization(login: $org) {
description
repositories(first: 2, after: $after) {
nodes {
url
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
}
totalCount
}
}
}
Query variables:
{
"org": "[your_org]",
"after": null
}
The result includes:
"pageInfo": {
"endCursor": "Y3Vyc29yOnYyOpHOAP4Uvg==",
"hasNextPage": true,
"hasPreviousPage": false
},
The next page gets called with adapted query variables:
{
"org": "[your_org]",
"after": "Y3Vyc29yOnYyOpHOAP4Uvg=="
}
The result looks similar to this
{
"data": {
"organization": {
"description": "from A to Beyond",
"repositories": {
"nodes": [
{
"url": "https://github.com/[your_org]/[repo_3]"
},
{
"url": "https://github.com/[your_org]/[repo_4]"
}
],
"pageInfo": {
"endCursor": "Y3Vyc29yOnYyOpHOARso4Q==",
"hasNextPage": true,
"hasPreviousPage": true
},
"totalCount": [some number]
}
}
}
}
You would continue to use the endCursor value of the response payload in the subsequent query variables until
"hasNextPage": false
Conclusion
Ordering repository lists with an order directive
orderBy: {field: $createdat, direction: $order}
as shown above breaks pagination in the GitHub GraphQL API.
That is also true for ordering only by creation date or direction.
I reported the described behaviour to GitHub support and will update this blog post once I learned more.
Make sure you do not apply an order directive like this to avoid broken pagination.
Any updates? Thanks for posting this!
The issue is fixed. I can’t reproduce it anymore.
The graphql query:
with these query variables:
results in the snippet below:
The subsequent request with the adapted query variables
succeeds as well as all subsequent requests that use the result endCursors.