What is Relay?
The relay spec defines some interfaces that GraphQL servers can follow to allow clients to interact with them in a more efficient way. The spec makes two core assumptions about a GraphQL server:
- It provides a mechanism for refetching an object
- It provides a description of how to page through connections.
You can read more about the relay spec here .
Relay implementation example
Suppose we have the following type:
We want it to have a globally unique ID, a way to retrieve a paginated results
list of it and a way to refetch if if necessary. For that, we need to inherit it
Node interface, annotate its attribute that will be used for
GlobalID generation with
relay.NodeID and implement its
Explaining what we did here:
relay.NodeID[int]. This makes
codea Private type, which will not be exposed to the GraphQL API, and also tells the
Nodeinterface that it should use its value to generate its
id: GlobalID!for the
We also implemented the
resolve_nodesabstract method. This method is responsible for retrieving the
Fruitinstances given its
codeis our id,
node_idswill be a list of codes as a string.
GlobalID gets generated by getting the base64 encoded version of the
<TypeName>:<NodeID> . In the example above, the
Fruit with a code of
1 would have its
Now we can expose it in the schema for retrieval and pagination like:
This will generate a schema like this:
With only that we have a way to query
node to retrieve any
type in our schema (which includes our
Fruit type), and also a way to retrieve
a list of fruits with pagination.
For example, to retrieve a single fruit given its unique ID:
Or to retrieve the first 10 fruits available:
The connection resolver for
relay.ListConnection should return one of those:
Generator[<NodeType>, Any, Any]
The node field
As demonstrated above, the
Node field can be used to retrieve/refetch any
object in the schema that implements the
It can be defined in in the
Query objects in 4 ways:
node: Node: This will define a field that accepts a
GlobalID!and returns a
Nodeinstance. This is the most basic way to define it.
node: Optional[Node]: The same as
Node, but if the given object doesn’t exist, it will return
node: List[Node]: This will define a field that accepts
[GlobalID!]!and returns a list of
Nodeinstances. They can even be from different types.
node: List[Optional[Node]]: The same as
List[Node], but the returned list can contain
nullvalues if the given objects don’t exist.
Custom connection pagination
relay.Connection class don’t implement any pagination logic, and
should be used as a base class to implement your own pagination logic. All you
need to do is implement the
The integration provides
relay.ListConnection , which implements a limit/offset
approach to paginate the results. This is a basic approach and might be enough
for most use cases.
relay.ListConnection implementes the limit/offset by using slices. That means
that you can override what the slice does by customizing the
method of the object returned by your nodes resolver.
For example, when working with
resolve_nodes can return a
QuerySet , meaning that the slice on it will translate to a
the SQL query, which will fetch only the data that is needed from the database.
Also note that if that object doesn’t have a
__getitem__ attribute, it will
itertools.islice to paginate it, meaning that when a generator is being
resolved it will only generate as much results as needed for the given
pagination, the worst case scenario being the last results needing to be
Now, suppose we want to implement a custom cursor-based pagination for our previous example. We can do something like this:
In the example above we specialized the
inheriting it from
relay.Connection[Fruit] . We could still keep it generic by
inheriting it from
relay.Connection[relay.NodeType] and then specialize it
when defining the field, making it possible to use our custom pagination logic
with more than one type.
Custom connection arguments
By default the connection will automatically insert some arguments for it to be able to paginate the results. Those are:
before: Returns the items in the list that come before the specified cursor
after: Returns the items in the list that come after the ” “specified cursor
first: Returns the first n items from the list
last: Returns the items in the list that come after the ” “specified cursor
You can still define extra arguments to be used by your own resolver or custom pagination logic. For example, suppose we want to return the pagination of all fruits whose name starts with a given string. We could do that like this:
This will generate a schema like this:
Convert the node to its proper type when resolving the connection
The connection expects that the resolver will return a list of objects that is a
subclass of its
NodeType . But there may be situations where you are resolving
something that needs to be converted to the proper type, like an ORM model.
In this case you can subclass the
provide a custom
resolve_node method to it, which by default returns the node
as is. For example:
The main advantage of this approach instead of converting it inside the custom
resolver is that the
Connection will paginate the
QuerySet first, which in
case of django will make sure that only the paginated results are fetched from
the database. After that, the
resolve_node function will be called for each
result to retrieve the correct object for it.
We used django for this example, but the same applies to any other other similar use case, like SQLAlchemy, etc.
The GlobalID scalar
GlobalID scalar is a special object that contains all the info necessary
to identify and retrieve a given object that implements the
It can for example be useful in a mutation, to receive and object and retrieve it in its resolver. For example:
In the example above, you can also access the type name directly with
id.type_name , the raw node ID with
id.id , or even resolve the type itself