While GraphQL specifies what queries, mutations, and object types should look like, Relay is a client-side implementation of an efficient data storage and (re-)fetching system that is designed to work with a GraphQL server.
To allow Relay to work its magic on the client side, all GraphQL queries and
mutations need to follow certain conventions.
utilities to help you make your server-side schemas Relay-compatible while
requiring only minimal changes to your existing code.
Absinthe.Relay supports three fundamental pieces of the Relay puzzle: nodes,
which are normal GraphQL objects with a unique global ID scheme; mutations,
which in Relay conform to a certain input and output structure; and
connections, which provide enhanced functionality around many-to-one lists
(most notably pagination).
Make sure you have the absinthe_relay package configured as a dependency for your application.
To add Relay support schemas should start with
use Absinthe.Relay.Schema, eg:
If you’re defining your types in a separate type module that you’re using via
import_types in your schema, use the
Notation module instead:
Now you’re ready to implement the Relay features you need.
To enable Relay to be clever about caching and (re-)fetching data objects, your server must assign a globally unique ID to each object before sending it down the wire. Absinthe will take care of this for you if you provide some additional information in your schema.
First of all, you must define a
:node interface in your schema. Rather than
do this manually,
Absinthe.Relay provides a macro so most of the configuration
is handled for you.
node interface in your schema:
For instance, if your query or mutation resolver returns:
Absinthe will pattern-match the value to determine that the object type is
:business. This becomes important when you configure your
:business type as a
While it may appear that your
:business object type only has two fields,
:employee_count, it actually has three. An
is configured for you because you used the
node object macro, and because the
:node interface knows how to identify the values returned from your resolvers,
:id field is automatically set-up to convert internal (in this case,
numeric) IDs to the global ID scheme – an opaque string (like
will be returned instead.
Important: the global ID is generated based on the object's
unique identifier, which by default is the value of its existing
field. This is convenient, because if you are using Ecto, the
:id database field is typically enough to uniquely identify an
object of a given type. It also means, however, that the internal
:id of a
node object will not be available to be queried as
- If you wish to generate your global IDs based on something other than the
:idfield (if, for instance, your internal IDs are returned as
_id), provide the
:id_fetcheroption (see the documentation).
- If you wish to make your internal ID queryable, you must return it as a
different field (eg, you could define an
:internal_idfield whose resolver extracts the raw, internal
:idvalue from the source map/struct).
Node query field
Ok, so your node objects provide a global
:id. How does Relay use it?
Relay expects you to provide a query field called
node that accepts a global
ID (as arg
:id) and returns the corresponding node object. Absinthe makes it
easy to set this up – use the
node field macro inside your
Notice that the resolver for
node field expects the first (args) argument to
:id. These are the node object type identifier and the
internal (non-global) ID, automatically parsed from the global ID. The resolver
looks up the correct value using the internal ID and returns a tuple, as usual.
For more information, see the documentation.
Converting node IDs to internal IDs for resolvers
If you need to parse a node (global) ID for use in a resolver, there is a
parsing_node_ids/2 that is automatically imported for you.
Here’s an example of how it works.
Let’s assume we have a field,
:employees, that returns a list of
objects for a given
:business_id – a node ID:
resolve_employees/2, we could certainly parse out the internal ID manually.
Here’s how that would look:
Obviously this can get a bit tedious if we have to do it often. Instead, we can
parsing_node_ids/2 to wrap our resolver function to do the parsing for
us, invoking our function with the internal ID instead. We just have to tell the
parsing_node_ids/2 what ID field arguments to parse and what the associated
types should be:
This leaves our resolver function virtually unchanged, and keeps our code much cleaner.
Relay sets some specific constraints around the way arguments and results for mutations are structured.
Relay expects mutations to accept exactly one argument,
clientMutationId, and expects to get it back, unchanged, as part of the
Absinthe.Relay abstracts these details away from the schema
designer, allowing them to focus on any other arguments needed or results
Important: Remember that input fields (and arguments in
general) cannot be of one of your
object types. Use
model complex argument types.
In this example, we accept a list of multiple
:person_input_object values to
insert people into a database.
payload macro introduces a Relay mutation,
input defines the fields
input argument), and
output defines the fields available as part
of the result.
See the documentation on Absinthe.Relay.Mutation for more information.
Referencing existing nodes in mutation inputs
Occasionally, your client may wish to make reference to an existing node in the mutation input (this happens particularly when manipulating the connection edges of a parent node).
Incoming IDs for node types may have to be converted to their internal
equivalents so you can persist changes to your backend. For this purpose, you
Absinthe.Relay.Node.from_global_id/2 to parse node (global) IDs
If, of course, your client knows the internal IDs (in a peer field to
:internal_id), you can depends on that ID – but we recommend that you use
node IDs as they are opaque values and it’s the more conventional practice.
Important: When using
from_global_id, remember to always
:type value to ensure the internal ID is for the type you expect,
and a global ID for the wrong type of node hasn't been mistakenly sent to the
Check the documentation for details on connections.