Pagination🔗

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))

Custom pagination strategies🔗

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))