Facebook's homegrown GraphQL client Relay finally was updated to its long-awaited 1.0 version: Relay Modern. The goals of the new version are threefold:
- Making Relay more approachable by simplifying its API
- Improving performance
- Splitting Relay into separate packages to make it more extensible and flexible
If you want to get an idea about the major changes in the new version, the updated (and majorly improved) documentation is a great place to start. Additionally, Sashko from the Apollo team wrote an excellent in-depth article shedding light on different aspects of the new Relay Modern.
In the following post, we're going to explore what it's like to migrate a project from Relay Classic (yes, that's what the old API is called now) to its Modern counterpart. The project we're going to convert is a simple Todo app (TodoMVC). You can find the code for it on Github. Note that the repo contains a branch
classic, representing the code before the conversion as well as a branch called
modern with the upgraded code.
Facebook not only released a new version of Relay, they also included a dedicated API to help transition to it: Relay Compat. This allows for developers to gradually migrate their existing code instead of having to go through one huge update process that would be accompanied by much sweat and tears.
Relay consists of two major parts:
- The API that allows developers to specify the data requirements for their components using on GraphQL fragments
- A runtime that receives the components' data requirements and implements the lower-level responsibilities related to networking and caching
The idea of Relay Compat is to allow updating the API while still working the old Relay runtime. This allows developer to update their apps component by component to using the new API without compatibility issues. The compatibility mode is available out-of-the-box with the new release candidate and can be accessed by importing from
react-relay/compat instead of just
Relay Docs: In order to incrementally convert an existing codebase, we will need to use the Relay Modern API while continuing to use the Relay Classic runtime until all components are converted.
The "Conversion Playbook"
To ease the migration process, Facebook also released a conversion playbook that provides stepwise instructions to adopt Relay Modern. We'll use the following 5 steps as guidance for our migration process:
- Updating the
react-relaydependency to version 1.0
- Gradually adjusting all React components to make use of the new API with Relay Compat
- Replacing all occurrences of
- Updating the runtime by introducing
- Cleaning up imports
In the following, we'll walk through these steps exploring each one in detail.
1. Installing Relay v1.0
Updating a project dependency to a new version with breaking changes can be scary. With Relay Modern, it's in fact not scary at all since all the old APIs are still accessible through
react-relay/classic. (Would be great if more tools could have such a nice upgrade path.)
The new version of Relay can simply be installed with the following command (not that we're using the
dev alias to get the release candidate of Relay):
To make sure the existing app still works, we now have to change all our imports from
Installing the Relay Compiler
There are a few more things we have to do to completely finish this step, that is adding the Relay compiler as well as updating and configuring our
A major part of the new release was untangling Relay into three major and independent components:
The descriptions for the packages are taken from the announcement blog post by Lee Byron and Joe Savona.
First, let's add the Relay compiler:
Once the Relay compiler is installed, we can use it by providing our source files and GraphQL schema as input arguments so that it can do its work:
schema.graphql represents our full GraphQL API expressed with the GraphQL IDL syntax. It might be worth setting up an npm script in the
package.json that makes this call:
A key differentiator of Relay compared to other clients like Apollo is that it requires a build step to validate your GraphQL code against the schema of your API making sure all your requests are valid. This build step is implemented as a Babel plugin.
To make it very clear and avoid any confusion at this point:
babel-relay-pluginis the old plugin (there also was an alternative called babel-plugin-react-relay)
babel-plugin-relayis the new plugin
During the migration process, we can simply replace the old plugin. Let's install this (development) dependency first:
Now we have to incorporate it by including it as a plugin in our
We're passing our schema to the
relay plugin too and tell it that we're in compatibility mode by specifying
true for the
2. Updating our code to the new Relay API
With Relay Modern, we can't use
Relay.Mutation any more. Instead, we'll use
FragmentContainer and a new pattern to declare mutations using the
Let's take a look at the
TodoList component from our app to get a feeling for what we have to in that conversion:
TodoList component declares its data requirements by means of a GraphQL fragment where it specifies that it needs access to all todos. It also pulls in the data requirements from the
When converting, we need to switch from
Relay.createContainer to using
createFragmentContainer to wrap the
TodoList component. Fragments are also not specified with
Relay.QL any more but instead the
graphql function is used:
Note that we're not using
getFragment() any more to include sub-dependencies. Instead we're using regular GraphQL syntax to refer to a fragment (
...Todo_todo). This means that all our fragments now live in a global namespace which greatly eases static analysis (read Sashko's article for more details on this). By convention, each fragment should be composed of the file name and the prop that it injects into the component:
<FileName>_<propName>. In compatibility mode, this convention is actually a hard requirement!
Automatic migration with
Todo component using
jscodeshift looks as follows:
Let's quickly compare the input and output of that process as well. Here's what
Todo looked like before the conversion:
And this is the output of
Notice that because of the new mutation API, we actually can get rid of all the fragments here since mutations don't declare own data requirements in Relay Modern any more. Consequently, we can manually shorten this code:
jscodeshiftcurrently just works for React components and not for mutations!
A major change in Relay Modern is a new mutation API. Instead of subclassing
Relay.Mutation, we can now call
commitMutation and pass in an environment as well as an object to describe the mutation. In that object, we'll provide all necessary information about the mutation and how we want Relay to perform it.
We can follow ideas from Relay Classic by providing a Mutator configuration. A new opportunity in Relay Modern however is to pass in an
updater function to imperatively update the store.
Let's take a look at the
ChangeTodoStatusMutation we're using to toggle the
complete status of a
Todo. This is what it looks like before the conversion:
After the conversion, it'll look as follows:
This allows us to import the mutation as a single object and simply call the exported
commit function on it:
After we've converted all components and mutations, the next step is to replace all occurrences of
Relay.Renderer with the new
QueryRenderer. In our example app, we're only using the
Relay.RootContainer at the moment:
It can be replaced with an instance of the
Notice that at this point we still need to provide a
Relay.Store from the Relay Classic API as the
environment to ensure the app still works properly.
4. Updating the Relay Runtime
This step will bring the actual performance benefits to your app, from the Relay docs: "Apps using the
RelayStaticEnvironment get to send persisted query IDs instead of the full query strings to the server, as well as much more optimized data normalizing and processing."
Generally, what we have to do at this step is provide an instance of
RelayStaticEnvironment to the
QueryRenderer instead of the current
Relay Environment is responsible for all the heavy lifting when it comes to data fetching and caching. It's instantiated with a Network Layer that specifies which GraphQL server Relay should talk to and additionaliy accepts configuration options, like HTTP headers for authentication or other purposes.
Here's how we create the Relay Environment in a dedicated file:
app.js, we can then directly import the environment and pass it to the
5. Clean up
The last step is simply cleaning up all the imports where we used
react-relay/compat. Since we ensured that all our components are now using the new Relay APIs, we don't have to pull in the old Relay code any more. So, at this point running through our source files and adjusting all Relay imports to
react-relay will do.
You can view the final state of the codebase here. Another important info for projects to be upgraded and making use of
react-relay-router is that Relay Modern currently isn't supported by react-router-relay, but can be included with a slight workaround as explained in this GitHub issue.
Congratulations, you've now learned how to transform your existing Relay project to Relay Modern!
Relay Modern is an extremely promising iteration of Relay. Facebook did a great job in making sure that the migration from the previous version can be done in a smooth and iterative manner. An improved documentation and stepwise instruction for the update process vastly contribute to that.