March 27, 2016
I’ve seen some confusion on Relay and GraphQL lately. GraphQL is so often used with Relay that I think sometimes we forget what a GraphQL server is, and what Relay adds on top of it. The goal of the next 3 posts is to try to clearly see the line between the two, and how to implement the Relay part in an existing GraphQL Server.
From Facebook’s words Relay is simply a
Framework for building data-driven React applications.
It is a wrapper around Relay components, enabling them to live next to the GraphQL queries they need to fulfil their data requirements. On the client side, Relay’s strength is it’s amazing client side cache. On the server side, Relay expects the GraphQL server to implement a certain spec for things to work perfectly.
Let’s take a look at what a Relay app needs from a GraphQL server that is different from usual. First, the Global Object Identification. Relay introduces the need for objects with unique identifiers, called “nodes”. With that concept, Relay can refetch arbitrary objects from a GraphQL server using their GID. On the GraphQL server, each object must have an id
field returning that GID
, and the root query type must have a node
field, used to query nodes globally.
Next, Connections. Connections are an Object that provide a standard way to slice and paginate results. Connections can provide PageInfo
, a way of telling the client if there are more results to fetch still. The Connection object provides a cursor for every item in it’s result set.
The last thing that Relay adds to a GraphQL server is the Input Object Mutation. Compared to regular GraphQL mutations, Relay mutations are similar, but try to standardize the way they are exposed and called. Every mutation takes 1 argument, an Input Object. This input object must always contain a clientMutationId
, and the GraphQL server must always return it in the response.
So as you see, 3 things are added on top of a vanila GraphQL server:
Each of these require quite a bit of work to implement in a GraphQL server. I’ve done the exercise myself, and will share what I’ve done to implement the Relay spec in a Ruby on Rails GraphQL server, using the GraphQL
gem as a base. I will divide the results in 3 blog posts, starting with this one. Let’s start by the Global Object Indentification
.
Let’s start by making our current Object Types compatible with Relay. If you remember from above, each object had to have an id
field. GraphQL has support for interface objects, this is a great use case for what we want to do. When using interfaces in the GraphQL
gem, the object implementing it “inherits” from it’s field.
So the first step is to create that interface type, let’s call it Node
.
# lib/graph/relay/node.rb
module Graph
module Relay
Node = GraphQL::InterfaceType.define do
name "Node"
field :id, !types.ID, property: :to_global_id
resolve_type -> (object) do
Graph::Relay::Node.possible_types.detect do |type|
object.is_a?(type.model)
end
end
end
end
end
This should not look too unfamiliar to you. We’re simply defining an Interface type using the GraphQL
gem. An interface must be able to resolve the type of an object. The code is pretty straight forward, we look through all of the possible_types
and find one that matches our object using type.model
.
Our field here, will simply resolve by calling object.to_global_id
. We need Global Identification, so I used something that Rails has by default: GlobalID. Calling object.to_global_id
transforms your ActiveRecord into a GlobalID
oject, a unique identifier for that particular record.
=> User.find(1).to_global_id
=> #<GlobalID:0x007f9abca6ecb8 @uri=#<URI::GID gid://app-name/User/1>>
You might’ve noticed that type.model
is not an attribute that types have using the GraphQL
gem. Since we’re using rails, I’ve augmented the normal GraphQL object type with my own attributes, like this:
# lib/graph/active_record_object_type.rb
module Graph
class ActiveRecordObjectType < GraphQL::ObjectType
attr_accessor :model
accepts_definitions(:model)
end
end
accepts_definitions
is a new addition to the GraphQL
gem. It basically lets you add custom fields to an ObjectType, while still being able to use the DSL GraphQL-ruby
provides.
With that being done, we can simply implement this interface in our existing fields, just like this:
# app/graph/user_type.rb
UserType = Graph::ActiveRecordObjectType.define do
name "User"
description "A User"
interfaces [Graph::Relay::Node]
model User
field :email, !types.String, property: :email
end
The two lines that interest us here are model User
which will indicate which ActiveRecord model our type is refering to, and interfaces [Graph::Relay::Node]
, which does exactly what we wanted to do!
There’s now only one thing left to do. Exposing a node
field on the query root. We can do this just like defining any other field:
field :node do
type Graph::Relay::Node
argument :id, !types.ID
resolve -> (object, args, _) do
gid = GlobalID.parse(args["id"])
GlobalID::Locator.locate(gid)
end
end
Our field accepts one argument, id
. And uses GlobalID::Locator
to get the corresponding object, pretty simple!
Lets test this!
First, let’s see if the object we query have the id
field available:
query getObjectId { viewer { currentUser { email } } }
Then, we should be able to query any object using the node
field:
query getObjectById { node(id: "gid://review-buddy/User/1") { ... on User { email } } }
Next week I’ll have a post up about implementing the second thing Relay needs from a GraphQL server: Connections
As always you can find me on Twitter @__xuorig__ or Github
Go back to Recent Posts ✍️
To me, the only thing missing to the GraphQL ecosystem and spec today are live updates. When our client applications require live updates from the server for data they care about, we need some...