Getting Started with Relay “Modern” for Building Isomorphic Web Apps

Written by tarkus | Published 2017/04/30
Tech Story Tags: graphql | react | relay | nodejs | javascript

TLDRvia the TL;DR App

Relay codename “Modern” is a new version of the popular GraphQL client library used at Facebook. It was redesigned with extensibility in mind, making it super flexible for integrating into any kind of web app, including isomorphic (or universal) web applications. But first…

Why GraphQL?

Well, if you look around, you will notice that GraphQL technology, which was also incubated at Facebook, is de-facto becoming the standard in developing backends for web and mobile apps, replacing RESTful APIs, JSON APIs, OData, and other similar specs — mainly because GraphQL is oriented towards developer experience and performance. Facebook developers absolutely nailed it.

Take a look at GitHub’s GraphQL implementation as an example — docs + playground, where you can see how it works “live” and try executing a couple of sample queries. Give that link to a front-end developer that never heard about GraphQL, and within a couple of hours, he will be able to grasp the central concepts, start using it, and get things done ^_^

If you’re still thinking about what tech to use for building a data API server for your next app, please look no further, GraphQL is an excellent choice for that use case. Also, it works perfect for building API gateways in microservice based architectures.

So, the question is not whether you need to use GraphQL or not :) But instead, which GraphQL client library best suits our project? At a bare minimum, you could just use HTML5 Fetch API. Let’s see what it looks like. You start by creating a couple of helper methods — fetchQuery(), commitMutation(), etc.

As you can see, the react-relay package provides just one top-level React component — QueryRenderer and three helper functions that are intended to be used for wrapping React components into Relay-enabled higher-order components. That’s it! The remaining five fields (graphql, fetchQuery etc) are simply re-exports from the relay-runtime module.

Well, actually react-relay exposes much more API methods and fields in react-relay/classic and react-relay/compat namespaces. But you don’t want to look into those unless you’re working with a legacy codebase that’s built on top of Relayv0.x. Now let’s see how to integrate Relay “Modern” into your project.

How to get started with Relay.js

I assume that you’re already familiar with React, Babel and Node.js and have a basic project structure using this stack. If you’re not a big fan of React, I think that the code samples below can be applied to another front-end framework or library as well with a few minor tweaks.

You start by installing all the packages mentioned previously by running:

yarn install relay-runtime@1.0.0-rc.3yarn install react-relay@1.0.0-rc.3yarn install relay-compiler@1.0.0-rc.3 --devyarn install babel-plugin-relay@1.0.1-rc.3 --dev

Including babel-plugin-relay into the list of plugins in your Babel configuration file (normally .babelrc or .babelrc.js):

Adding an npm script that will run Relay Compiler. So, the package.json file in the root of your project would contain the following entries:

Now you need to copy and paste GraphQL schema from your data API server into your project by saving it to src/schema.graphql file (see example). This file is going to be used by Relay Compiler. And, as a side benefit, having this text-based schema (as opposed to JSON) under the source control will allow you and your front-end team to easily see how your GraphQL model evolves over time. Feel free to automate this task if needed.

Now you can try inserting some GraphQL queries in your code (inside the src folder) and run yarn run relay command that will compile these text queries into JavaScript.

Alternatively, you can run Relay Compiler in watch mode:

yarn run relay -- --watch

Just keep in mind, that if you try to use some fields in your GraphQL queries that are missing in thesrc/schema.graphql file, Relay Compiler with throw an error.

How does a GraphQL query must look like? You need to import graphql object from either relay-runtime or react-relay module. And use tagged template literals with it. Here is an example:

Let’s assume for a moment that your src/schema.graphql file contains a schema with viewer and posts top-level fields. Then if you would run yarn run relay, it would generate __generated__/Example.graphql.js file in the same folder with the source file containing that GraphQL query. And after you compile your original source code with Babel, the tagged template literal string above will be replaced with const query = () => require('__generated__/Example.graphql').

An important thing to note is that Relay Compiler won’t work if your code contains anonymous queries or fragments such query { viewer { email } } vs query ExampleQuery { viewer { email } }.

You can compose a top-level GraphQL query from fragments referring those fragments by their names. For example:

Even if these exact same queries would be located in different files, Relay Compiler is smart enough to build the dependency tree, compile and optimize them. That’s why it requires all the queries and fragments to have their own unique names.

Now, in order to pull data for all the components in the example above (Layout, Toolbar, PostList), we need to fetch data only for the top-level query as the following code demonstrates:

Don’t pay much attention to the render function, but instead see how the fetchQuery() helper method from the relay-runtime module is being used.

You may wonder, where is the environment variable coming from? In order to use Relay you need to initialize the so-called Relay environment. Luckily, therelay-runtime module provides all the necessary tools for that. This “environment” will slightly differ depending on whether your code is being executed in a browser or Node.js environment.

For example, in a browser you may want to use HTML Fetch API in combination with [whatwg-fetch](https://github.com/github/fetch) polyfill and on the server, you can use the[node-fetch](https://github.com/bitinn/node-fetch) module that has exactly the same API but is designed for running in Node. Thanks to Webpack and [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch) module, you don’t have to think much about how to bundle the right library into your production code, it’s just as simple as doing import fetch from 'isomorphic-fetch'.

What else would differ? The base URL of your GraphQL endpoint. For example, in a browser Relay may fetch data from the /graphql endpoint, using a URL path string relative to your website’s domain name. But on the server, it must be a full URL, something like http://api:8080/graphql.

Most likely you will want to create a factory method that given a base URL and a couple of other options will initialize the correct React environment plus a couple of helper methods to be used in either client-side or server-side code. Here is how it may look like:

See src/api.js in https://github.com/kriasoft/react-starter-kit

This factory method initializes Relay environment as well as binds it to the fetchQuery(), commitMutation() methods so you don’t need to pass it every time you need to send a GraphQL query to the server. Here is an example of how it can be used:

Relay ❤ React

Now let’s see how to decorate your React components with GraphQL query fragments, so that you wouldn’t need to manually pass all the props down the UI hierarchy, but let Relay to lookup the required data automatically per component. In order to do so, all you need to do is to use one of these three helper methods from react-relay module:

  • **createFragmentContainer()** — Composes a React component class, returning a new class ([FragmentContainer](https://facebook.github.io/relay/docs/fragment-container.html)) that intercepts props, resolving them with the provided fragments and subscribing for updates.
  • **createRefetchContainer()** — Wraps a React component class into [RefetchContainer](https://facebook.github.io/relay/docs/refetch-container.html) that first renders like a regular FragmentContainer but has the option to execute a new query with different variables and render the response of that query instead when the request comes back.
  • createPaginationContainer() — Wraps a React component class into [PaginationContainer](https://facebook.github.io/relay/docs/pagination-container.html) that is designed to simplify the workflow of loading more items in a list — in many cases, we don't want to fetch all the data at once but lazily load more data. It relies on a GraphQL server exposing connections in a standardized way. For a detailed spec, please check out this page.

Where Toolbar_viewer is the name of the fragment, by convention it uses the name of the file (Toolbar) + underscore + the name of property under which that data must become available via this.props.

When you initialize this component, you need to pass viewer={…} prop containing record ID and FragmentComponent will pull all the required fields from local Relay store by that ID (email and isAdmin in that case). For example, in the parent Layout component you would need to instantiate Toolbar like this:

If, instead of Layout_viewer, you name your fragment just Layout, then the language field is going to be available via this.props.data (versus this.props.viewer).

OK, now let’s see how to integrate Relay with a single-page application router.

Relay ❤ Universal Router

As was mentioned previously, Relay comes with [QueryRenderer](https://facebook.github.io/relay/docs/query-renderer.html) top-level component the purpose of which is to mount some temporary markup into the DOM, e.g. “Loading…”, given a top-level GraphQL query, start fetching required data from the server, and once data fetching is complete, render the actual application screen (page).

I’m sure this QueryRenderer class does not suit all type of apps well, most likely you will want to either copy-paste and customize it for your app, or even use an alternative solution that will serve the similar purpose.

In the case of isomorphic apps, there is no need in rendering a temporary “Loading” screen. And also, it might be a better idea to pre-load all the data before rendering your app with React. Let’s see how to implement that by using [Universal Router](https://github.com/kriasoft/universal-router) library — that might be already familiar to you via React Starter Kit project.

If you never heard about Universal Router, it’s a simple middleware-style routing solution that is framework agnostic (works well with any front-end framework) and purposely looks pretty much similar to the Express.js router making it simpler to adopt in isomorphic web apps.

You need to compile a list of routes as a normal JavaScript array, where each item has path, action and optionally children properties. A typical route may look like this:

Where api argument is the exact same object that we initialized in one of the previous examples being passed to the route handler (action()) method as a context variable.

You initialize a router by calling new Router(routes) and then execute its .resolve({ path, …context }) method to find and execute a route that matches the provided URL path string. Then all you need to do is just to render the returned React component to DOM on the client or into an HTML string on the server as the following sample code demonstrates:

Summary

Relay “Modern” is really great. The code samples above demonstrate how easily it can be integrated into any project (not necessarily React). Even if you start by using its basic features, you will still be able to write data fetching related code much more efficiently than without Relay. In the future posts, I will try to cover more advanced Relay features. Stay tuned :)

And if you want to see a working example of React+Relay integration, please visit React Starter Kit project on GitHub. Also, there is a sibling repo — Node.js API Starter that may help you to build a GraphQL API backend. I’m quite successfully using these two boilerplates in all my projects, so if you need any advice, don’t hesitate to get in touch on Twitter (@koistya).


Published by HackerNoon on 2017/04/30