After bootstrapping a new project (by running yarn create react-app ), you will need to install Relay’s run-time and dev dependencies:

$ yarn add relay-runtime react-relay

$ yarn add relay-compiler babel-plugin-relay --dev

Next, create “setup.js” file in the root of your project with the following contents:

const fs = require('fs');

const path = require('path');



const file = path.resolve('./node_modules/babel-preset-react-app/index.js');

const text = fs.readFileSync(file, 'utf8');



if (!text.includes('babel-plugin-relay')) {

if (text.includes('const plugins = [')) {

text = text.replace(

'const plugins = [',

"const plugins = [

require.resolve('babel-plugin-relay'),",

);

fs.writeFileSync(file, text, 'utf8');

} else {

throw new Error(`Failed to inject babel-plugin-relay.`);

}

}

Finally, update the “scripts” section in package.json file to run this script before react-scripts <command> :

"scripts": {

"build": "node ./setup && react-scripts build",

"test": "node ./setup && react-scripts test --env=jsdom",

"start": "node ./setup && react-scripts start"

}

Now, whenever you run yarn build , yarn test , or yarn start the Babel compiler will use “babel-plugin-relay” as required by Relay Modern.

Configuring Relay Compiler

The next step is to configure Relay Compiler, the purpose of which is to find all the graphql`…` queries in your code, pre-compile them and save to __generated__/*.graphq.js files consumed by “babel-plugin-relay” from the previous step.

In order to do so, first, you need to save the text-based representation of your GraphQL schema into a file inside of your project, assuming that your React app is intended to work with some external GraphQL API. And then add one more npm script into package.json that will run relay-compiler . It may look something like this:

For this exercise, we are going to use an existing GraphQL demo API hosted at https://graphql-demo.kriasoft.com (source code).

Before you can run yarn relay , make sure that you have Facebook’s Watchman tool installed on you dev machine.

Now, whenever you change some GraphQL/Relay query in your code, you would need to run yarn relay over again, or keep it running in a separate shell window in watch mode by running yarn relay -- --watch .

Q: Do I need to excluded generated by Relay Compiler files from my source control by appending __generate__/** to .gitignore ?

A: Good question :) Yes, as a rule of thumb you don’t want any auto-generated files in your source code repository.

Try putting the following piece of code into any of your source files (e.g. into src/index.js ) and running yarn relay && yarn build :

import { graphql } from 'relay-runtime'; graphql`query { me { displayName } }`;

If you can build the project without any errors, most likely that the previous two steps are done correctly.

How to initialize a Relay client (environment)?

This part is simple, just follow the official docs. Your Relay client (environment) will look something like this ( src/relay.js ):

You can even use it directly without “react-relay” package if you want to. Here is an example:

import { graphql, fetchQuery } from 'relay-runtime';

import relay from './relay'; fetchQuery(

relay,

graphql`query { me { displayName } }`

).then(...);

In a real-world project, you would need to replace the hardcoded API URL in the code sample above with something like this:

fetch(process.env.REACT_APP_API || 'http://localhost:8080', ...)

..so you could easily run your app in dev/test/prod environments. For more information about injecting environment variables into your app, refer to Create React App user guide.

How to use <QueryRenderer /> component

Relay Modern comes with a handy <QueryRenderer /> component that you can use at the top level of your app, somewhat similarly to Redux’s <Provider> component. It will pass Relay’s environment (including Relay store and HTTP client) down to all the children React components via context. Also it will handle updates for you, so you won’t need to re-render your components manually after running mutations.

The logic looks like this — whenever a user opens your site, or navigates from one page / screen to another, you find the corresponding GraphQL query and a list of parameters (variables) for that page, pass it to <QueryRenderer> and let it do the job (it will fetch missing data and re-render affected children component).

Your top-level React component ( src/App ) will look something like this:

The code sample above uses “history” npm module for navigation and “universal-router” for resolving URL path to a route. The contents of src/history.js looks as follows:

import createHistory from 'history/createBrowserHistory';

export default createHistory();

It is a cross-browser wrapper around HTML5 History API, that allows you to respond to any changes caused by changes in window.location initiated on the client-side, as well as modifying that state, e.g. by calling history.push(..) .

The contents of src/router.js is a little bit more interesting. Given the list of application routes and a global router handler function, it initializes and exports an instance of the universal-router class.

BTW, in case with GraphQL/Relay it makes sense to use declarative routing approach (as opposed to imperative routes), meaning that all your routes contain just metadata and no handlers. At a bare minimum, each of your route can contain a URL path string (pattern), GraphQL query, and component to render, for example:

const routes = [

{

path: '/posts/:id',

query: graphql`query routeStoryQuery($id: ID!) {

post: node(id: $id) { ...Post_post }

}`,

component: () =>

import(/* webpackChunkName: 'post' */, './Post')

},

...

]

Now you can initialize the router by providing the list of routes and a global router handler (resolver) function, somewhat similar to this:

import Router from 'universal-router';

import { graphql } from 'relay-runtime'; const routes = [ ... ]; // see above export default new Router(routes, {

resolveRoute({ route, next }, params) {

if (!route.component) next();

return {

query: route.query,

component: route.component(),

variables: params,

};

}

});

You would need to tweak this example a little to cover more use cases. The idea is that when the corresponding route is found (URL matched), it should start downloading the JavaScript chunk for that page / screen and at the same time pass query and variables to QueryRenderer (see src/App example above), effectively triggering data fetching for that route. The only issue, is that when QueryRenderer has finished fetching data, you need to make sure that async chunk loading has also finished its work (promise resolve) before you can render the page, for that purpose you use an intermediate (AppRenderer) component.

You can put as much or as little metadata in your routes to suite your app, for example, you can put authentication rules like so:

{

path: '/admin/users',

query: graphql`...`,

component: () => ...,

authorize: { roles: ['admin'] } // or, "authorize: true" etc.

}

With GraphQL / Relay you may want to fetch data for all of your components on a particular page / screen at once (as opposed to fetching data individually for each nested level of components), otherwise you would degrade the value of GraphQL a little.