Relay

Introduction

Picking the right data architecture in a client-server web app can be difficult. There are a myriad of decisions to be made when you try to implement a data architecture. For example:

  • How will we fetch data from the server?
  • What do we do if that fetch fails?
  • How do we query dependencies and relationships between data
  • How do we keep data consistent across the app?
  • How can we keep developers from breaking each others' components accidentally?
  • How can we move faster and write our app-specific functionality rather than spending our time pushing data back and forth from our server?

Thankfully, Relay has a theory on all of these issues. Even better: it's an implementation of that theory and it's a joy to use, once you get it set up. But it's natural to ask: What is Relay?

Relay is a library which connects your React components to your API server.

We talked about GraphQL earlier in the book and it may not be immediately clear what the relationship is between Relay, GraphQL, and React. You can think of Relay as the glue between GraphQL and React.

Relay is great because:

  • components declare the data they need to operate
  • it manages how and when we fetch data
  • it intelligently aggregates queries, so we don't fetch more than we need
  • it gives us clear patterns for navigating relationships between our objects and mutating them

One of the most helpful things about Relay that we'll see in this chapter is that the GraphQL queries are co-located with the component. This gives us the benefit of being able to see a clear specification of the values needed to render a component directly alongside the component itself.

Each component specifies what it needs and Relay figures out if there's any duplication, before making the query. Relay will aggregate the queries, process the responses, and then hand back to each component just the data it asked for.

GraphQL can be used independent of Relay. You can, in theory, create a GraphQL server to have more-or-less any schema you want. However, Relay has a specification that your GraphQL server must follow in order to be Relay-compatible. We'll talk about those constraints.

What We're Going to Cover

In this chapter we're going to walk through a practical tutorial on how to setup and use Relay in a client app.

Relay depends on a GraphQL server, which we've provided in the sample code, but we're not going to talk about the implementation details of that server.

In this chapter we're going to:

  • explain the various concepts of Relay
  • describe how to setup Relay in our app (with routing)
  • show how to fetch data from Relay into our components
  • show how to update data on our server using mutations
  • highlight tips and tricks about Relay along the way

By the end of this chapter you'll understand what Relay is, how to use it, and have a firm foundation for integrating Relay into your own apps.

What We're Building

The Client

In this chapter, we're going to build a simple bookstore. We'll have three pages:

  • A book listing page, which shows all of the books we have for sale.

Books Store Page

  • An author bio page, which shows an author's information and the books they've authored.

Author Page

  • A book listing page, which shows a single book and the authors for that book. We'll also edit a book on this page.

Book Listing Page

The data model here is straightforward: a book has (and belongs to) many authors.

This data is provided to our app via an API server.

The Server

We've provided a Relay-compatible GraphQL demo server in the sample code with this app. In this chapter we aren't going to discuss the implementation details of the server. If you're interested in building GraphQL servers, checkout the chapter "GraphQL Server."

This server was created with the library graffiti-mongoose. If you're using MongoDB and mongoose, then graffiti-mongoose is a fantastic choice for adapting your models to GraphQL. graffiti-mongoose takes your mongoose models and automatically generates a Relay-compatible GraphQL server.

That said, there are libraries that can help you generate GraphQL from your models for every popular language. Checkout the list awesome-graphql to see what libraries are available.

Try The App

You can find the project for this chapter inside the relay folder of the code download:


$ cd relay/bookstore

We've included the completed server and completed client. Before we can run them, we need to run npm install on both the client and the server:


$ npm install
$ cd client
$ npm install
$ cd ..

Next, you can run them individually in two separate tabs with:


# tab one
$ npm run server
# tab two
$ npm run client

Or we've provided a convenience command that will run them both with:


$ npm start

When they're running, you should be able to access the GraphQL server at http://localhost:3001 and the client app at http://localhost:3000.

In this chapter, we're going to be spending time in our browser looking at both the client and the server. We've installed the GraphQL GUI tool "GraphiQL" on this demo server.

Relay is based on GraphQL, and so to get a better understanding of how it works, we're going to be running a few queries "by hand" in this GraphiQL interface.

How to Use This Chapter

While we will introduce all of the terminology of Relay, this chapter is intended to be a guide to working with Relay and not the API documentation.

As you're reading this chapter, feel free to cross-reference the official docs to dig in to the details.

Prerequisites

This is an advanced chapter. We're going to focus on using Relay and not re-cover the basics of writing React apps. It's assumed that you're somewhat comfortable with writing components, using props, JSX, and loading third party libraries. Relay also requires GraphQL, and so we assume you have some familiarity with GraphQL as well.

If you don't feel comfortable with these things, we cover all of them earlier in the book.

Relay 1

Currently this chapter covers Relay 1.x. There is a new version of Relay in the works, but don't let that stop you from learning Relay 1. There is no public release date and Facebook employee Jaseph Savona stated on GitHub that Relay 2 has "definite API differences, but the core concepts are the same and the API changes serve to make Relay simpler and more predictable"

Some good news here is that the underlying GraphQL specification doesn't change. So while, yes, Relay will have a v2 in the future. We can still use Relay 1 in our production apps today.

If you're interested more in the future of Relay, checkout Relay: State of the State.

Guide to the Code Structure

In this chapter, we've provided the completed version of the app in relay/bookstore.

In order to break up the concepts in to more digestible bites, we've broken up some of the components into steps. We've included these intermediate files in relay/bookstore/client/steps.

Our Relay app contains a server, a client, and several build tools -- these all add up to quite a lot of files and directories. Here's a brief overview of some of the directories and files you'll find in our project. (Don't worry if some of this is unfamiliar, we'll explain everything you need to know in this chapter):


-- bookstore
    |-- README.md
    |-- client
    |   |-- config                         // client configuration
    |   |   |-- babelRelayPlugin.js        // our custom babel plugin
    |   |   |-- webpack.config.dev.js      // webpack configuration
    |   |   `-- webpack.config.prod.js
    |   |-- package.json
    |   |-- public                         // images and index.html
    |   |   |-- images/
    |   |   `-- index.html
    |   |-- scripts                        // helper scripts
    |   |   |-- build.js
    |   |   |-- start.js
    |   |   `-- test.js
    |   `-- src
    |       |-- components                 // our components are here
    |       |   |-- App.js
    |       |   |-- AuthorPage.js
    |       |   |-- BookItem.js
    |       |   |-- BookPage.js
    |       |   |-- BooksPage.js
    |       |   |-- FancyBook.js
    |       |   `-- TopBar.js
    |       |-- data                       // graphql metadata
    |       |   |-- schema.graphql
    |       |   `-- schema.json
    |       |-- index.js
    |       |-- mutations                  // mutations make changes
    |       |   `-- UpdateBookMutation.js
    |       |-- routes.js                  // our routes
    |       |-- steps/                     // intermediate files
    |       `-- styles                     // css styles here
    |-- models.js                          // server models
    |-- package.json
    |-- schema.js                          // graphql schema on the server
    |-- server.js                          // our server definition
    |-- start-client.js
    |-- start-server.js
    `-- tools
        `-- update-schema.js               // a helper for generating the schema

Feel free to poke around at the sample code we've provided, but don't worry about understanding every file just yet. We'll cover all of the important parts.

Relay is a Data Architecture

Relay is a JavaScript library that runs on the client-side. You connect Relay to your React components and then Relay will fetch data from your server. Of course, your server also needs to conform to Relay's protocol, and we'll talk about that, too.

Relay is designed to be the data-backbone for your React app. This means that, ideally, we'd use Relay for all of our data loading and for maintaining the central, authoritative state for our application.

Because Relay has its own store it's able to cache data and resolve queries efficiently. If you have two components that are concerned with the same data, Relay will combine the two queries into one and then distribute the appropriate data to each component. This has the duel benefit of minimizing the number of server calls we need but still allowing the individual components the ability to specify locally what data they need.

Because Relay holds a central store of your data, this means it's not really compatible with other data architectures that keep a central store, like Redux. You can't have two central stores of state and so this makes the current versions of Redux and Relay essentially incompatible.

Apollo

If you're already using Redux but you'd like to try Relay, there's still hope: the Apollo project is a Relay-inspired library that is based on Redux. If you're careful, you can retrofit Apollo into your existing Redux app.

We're not going to talk about Apollo in this chapter, but it's a fantastic library and it's absolutely worth looking into for your app.

Relay GraphQL Conventions

One thing we need to clarify is the relationship between Relay and GraphQL.

Our GraphQL server defines our GraphQL schema and how to resolve queries against that schema. GraphQL itself lets you define a wide variety of schemas. For the most part, it doesn't dictate what the structure of your schema should be.

Relay defines a set of conventions on top of GraphQL. In order to use Relay, your GraphQL server must adhere to a specific set of guidelines.

At a high level, these conventions are:

  1. A way to fetch any object by ID (regardless of type)
  2. A way to traverse relationships between objects (called "connections") with pagination
  3. A structure around changing data with mutations

These three requirements allow us to build sophisticated, efficient apps. In this chapter, we'll make these generic guidelines concrete.

Let's explore implementations of these three Relay conventions directly on our GraphQL server. Then later in the chapter we'll implement them in our client app.

Relay Specification Official Docs

You can checkout Facebook's Official Documentation on the Relay/GraphQL specification here

Exploring Relay Conventions in GraphQL

Make sure you have the GraphQL server running, as described above, and open it in your browser at the address localhost:3001.

Remember that GraphiQL reads our schema and provides a documentation explorer to navigate the types. Click on the "Docs" link in GraphiQL to show the "Root Types" of our schema.


GraphiQL Interface with Docs

Fetching Objects By ID

In our server we have two models: Author and Book. The first thing we're going to do is look-up a specific object by ID.

Imagine that we want to create a page that shows the information about a particular author. We might have a URL such as /authors/abc123, where abc123 is the ID of the author. We'd want Relay to ask our server "what is the information for the author abc123?" In that case, we'd use a GraphQL query like we're going to define below.

However, we have a bit of a chicken and egg problem at the moment: we don't know the IDs of any individual record.

So let's load the entire list of authors' names and ids and then take note of one of the IDs.

Enter the following query into GraphiQL:


query {
  authors {
    id
    name
  }
}

And click the play button:


GraphiQL Authors with IDs

Now that we have an author ID, we can query author to get the author with a specific id:

Query:


query {
  author(id: "QXV0aG9yOjY1ZmJlZjA1ZTAxZmFjMzkwY2IzZmEwNw==") {
    id
    name
  }
}

Response:


{
  "data": {
    "author": {
      "id": "QXV0aG9yOjY1ZmJlZjA1ZTAxZmFjMzkwY2IzZmEwNw==",
      "name": "Anthony Accomazzo"
    }
  }
}

While this is handy, from the author query we can only receive an object of type Author, so it doesn't fulfill the requirement of the Relay specification. The Relay specification says that we need to have a way to query any object (Node) via the node query.