In this section, we'll cover the everything necessary for adding pagination
to your GraphQL schema using the Relay Connection
specification.
For Relay-compliant clients, see the Global Object IDs section
for adding support for the Node
interface.
Here are the models used in the examples below:
| from django.db import models
class Person(models.Model):
name = models.CharField(max_length=255)
email = models.EmailField(unique=True)
class Task(models.Model):
name = models.CharField(max_length=255)
done = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
assignees = models.ManyToManyField(Person)
|
Connection
To support pagination, you need to wrap QueryTypes
in Entrypoints
with the Connection
class.
| from undine import Entrypoint, QueryType, RootType
from undine.relay import Connection
from .models import Task
class TaskType(QueryType[Task]): ...
class Query(RootType):
paged_tasks = Entrypoint(Connection(TaskType))
|
Querying this Entrypoint
will return a response like this:
| {
"data": {
"pagedTasks": {
"totalCount": 3,
"pageInfo": {
"hasNextPage": false,
"hasPreviousPage": false,
"startCursor": "YXJyYXljb25uZWN0aW9uOjA=",
"endCursor": "YXJyYXljb25uZWN0aW9uOjI="
},
"edges": [
{
"cursor": "YXJyYXljb25uZWN0aW9uOjA=",
"node": {
"pk": 1,
"name": "Task 1",
"done": false
}
},
{
"cursor": "YXJyYXljb25uZWN0aW9uOjE=",
"node": {
"pk": 2,
"name": "Task 2",
"done": true
}
},
{
"cursor": "YXJyYXljb25uZWN0aW9uOjI=",
"node": {
"pk": 3,
"name": "Task 3",
"done": false
}
}
]
}
}
}
|
One addition to the Relay specification is the inclusion of a totalCount
field,
which returns the total number of items that can be queried from the Connection
.
Many-related Fields
can also be paginated using the Connection
class.
| from undine import Entrypoint, Field, QueryType, RootType
from undine.relay import Connection
from .models import Person, Task
class PersonType(QueryType[Person]): ...
class TaskType(QueryType[Task]):
assignees = Field(Connection(PersonType))
class Query(RootType):
paged_tasks = Entrypoint(Connection(TaskType))
|
Page size
The default page size of a Connection
is set by the CONNECTION_PAGE_SIZE
setting. You can use a different page size by providing the page_size
argument to the Connection
.
| from undine import Entrypoint, QueryType, RootType
from undine.relay import Connection
from .models import Task
class TaskType(QueryType[Task]): ...
class Query(RootType):
paged_tasks = Entrypoint(Connection(TaskType, page_size=20))
|
Both top-level and nested connections are optimized by the Optimizer
to only query the items that are needed for the current page. Both optimizations
should be quite performant, but calculating totalCount
for nested connections
can be slow, since it requires a subquery for each parent item.
If you need to modify the pagination behavior, you can do so by providing a custom
PaginationHandler
to the Connection
.
| from undine import Entrypoint, QueryType, RootType
from undine.relay import Connection, PaginationHandler
from .models import Task
class CustomPaginationHandler(PaginationHandler):
"""Custom pagination logic."""
class TaskType(QueryType[Task]): ...
class Query(RootType):
paged_tasks = Entrypoint(Connection(TaskType, pagination_handler=CustomPaginationHandler))
|