Implementing GraphQL Relay Node support in Hot Chocolate
25 Jan 2021 by Nigel SampsonWhat is Relay?
The GraphQL spec isn’t very prescriptive on the structure of your schema leaving the design completely in your hands. This means we’re likely to see quite a few different approaches to certain patterns between different services. One specification that has become pretty common across services is Relay.
If you check out their webpage you’ll notice that it advertises itself with
Relay is a JavaScript framework for building data-driven React applications powered by GraphQL, designed from the ground up to be easy to use, extensible and, most of all, performant. Relay accomplishes this with static queries and ahead-of-time code generation.
In order to achieve this Relay has to make some assumptions about the structure of your GraphQL service, these are documented at GraphQL Server Specification. We won’t described it in detail here but ultimately it covers three areas.
- Being able to fetch arbitrary objects by id.
- Paging one to many relationships between objects.
- Mutation inputs and paylods.
For this post we’ll cover implementing part of this specification using Hot Chocolate, in particular the first requirement around arbitrary objects by id.
The server specific defines an interface Node
to designate service objects and being able to accessed globally by id.
interface Node {
id: ID!
}
Objects implementing this interface may look something like
type Product implements Node {
id: ID!
name: String!
category: ProductCategory!
}
type Order implements Node {
id: ID!
customer: Customer!
createdOn: Instant!
}
The specification also defines a top level query field that takes an ID
and returns a Node
.
type Query {
node(id: ID!): Node
}
This would allow to construct queries that look like
query {
node(id: "Tm90aWZpY2F0aW9uCmQxNTox") {
id
... on Product {
name
category
}
}
}
It should be obvious from this that the id values for each our nodes should be globally unique.
So what’s the value here? This patterns means we may not need to build top level fields to query different objects by id but all can go through this node
field. In provides standards around identifiers as well.
Implementing in Hot Chocolate
Our first step will be enabling Relay functionality on our server, this is done during our server setup.
services
.AddGraphQLServer()
.EnableRelaySupport()
.AddQueryType<QueryType>();
Once this is done we can no add Node
support to any of our types. Let’s assume we have a ProductType
class describing Product
.
public class ProductType : ObjectType<Product>
{
protected override void Configure(IObjectTypeDescriptor<Product> descriptor)
{
descriptor
.ImplementsNode()
.IdField(n => n.Id)
.ResolveNode(async (context, id) =>
{
var productRepository = context.Service<IProductRepository>();
return await productRepository.GetById(id);
});
}
}
Here we’re doing three things.
- Saying that
Product
will implement theNode
interface. - Describe which property on the
Product
will be used for theNode.id
. - A resolver to take the id and return the
Product
.
So what is Hot Chocolate doing here?
It’s adding a field id
to the Product
object the resolves to creating a globally unique identifier. It does this by encoding the type information into the id. For instance if internally our product id was the integer 42
then on our GraphQL service the id value will look something like UHJvZHVjdAppNDI=
which decoded includes the type as well as the id. This also means that when the a query to the node
field is resolved because the id contains the type information then it can route the query to the resolver in question.
In later posts we’ll talk about connections and mutations.