A deep-dive into Relay, the friendly, & opinionated GraphQL client!
Published: 2020-07-09 04:57:42

---
title: A deep-dive into Relay, the friendly, & opinionated GraphQL client!
published: true
date: 2020-05-28 20:04:46 UTC
tags: 
canonical_url: https://hasura.io/blog/deep-dive-into-relay-graphql-client/
---

🚀 Hasura now has [Relay support](https://github.com/hasura/graphql-engine/releases/tag/v1.3.0-beta.1) and we'd love [your feedback!](https://discordapp.com/invite/hasura) Get an auto-generated Relay backend on top of your database.

<!--kg-card-end: html-->

> A **relay** network is a broad class of network topology commonly used in wireless networks, where the source and destination are interconnected by means of some **nodes**. - [Wikipedia](https://en.wikipedia.org/wiki/Relay_network)

[Relay](https://relay.dev/docs/en/experimental/a-guided-tour-of-relay) is a JavaScript framework for declaratively fetching and managing GraphQL data. "Sounds approachable enough," you say? Not so fast. Relay is an amazing friend once you win over its heart, but it doesn't bond with just anybody. There are strict conventions you must follow in order to become worthy in its eyes.

The most impressive fact about Relay (besides having eyes) is that no matter how hard you try, IT WON'T LET YOU FAIL. With its strict conventions, it **removes opportunities for developer errors** at every turn. By standardizing everything, it gives you no choice but to write **performant and type-safe apps**.

<!--kg-card-begin: html-->

💡 Facebook [originally created](https://reactjs.org/blog/2015/02/20/introducing-relay-and-graphql.html) GraphQL and Relay in the context of trying to improve UX for very slow network connections.

<!--kg-card-end: html-->

<!--kg-card-begin: html-->

💡 The examples in this article are based on the [experimental](https://relay.dev/docs/en/experimental/step-by-step) release of Relay Hooks, a new Relay API that supports React [Concurrent Mode](https://reactjs.org/docs/concurrent-mode-intro.html) and [Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html).

<!--kg-card-end: html-->

## TLDR: Is Relay for me?

✅ Yes, if you'd like an opinionated, robust framework that will keep your app sturdy and performant as it increases in size and complexity.

❌ No, if you'd like freedom to follow your own path. Go with a less opinionated, more flexible GraphQL client like [Apollo](https://www.apollographql.com/docs/react/).

* * *

- [Anatomy of Relay](#it-s-all-well-and-good-to-have-opinions-but-how-does-relay-enforce-them)
<!--kg-card-begin: html-->
- [Colocation](#what-are-these-conventions-you-speak-of)
  - [Fragments](#but-how-do-we-split-the-data-definitions-into-separate-components)
  - [Data masking](#data-masking)
- [Relay compiler](#sounds-tight-but-can-you-really-be-defensive-without-strong-typing)
  - [Automatic type generation](#sounds-tight-but-can-you-really-be-defensive-without-strong-typing)
  - [Transforms](#transforms)
  - [Persisted queries](#persisted-queries)
- [Query best practices](#back-to-the-root-query-what-does-it-look-like)
  - [with `useLazyLoadQuery`](#back-to-the-root-query-what-does-it-look-like)
  - [with `usePreloadedQuery` and `Suspense`](#what-if-i-wanted-to-be-less-lazy)
<!--kg-card-end: html-->
- [Error handling](#what-if-there-s-an-error)
- [Fragment `@arguments`](#how-can-i-access-variables-in-fragments)
<!--kg-card-begin: html-->
- [Relay's GraphQL server specification](#i-want-to-write-a-relay-compatible-graphql-server)
  - [Globally unique `id`s and the `Node` interface](#1-a-mechanism-for-refetching-an-object)
  - [Pagination through connections](#2-a-description-of-how-to-page-through-connections)
  - [Mutation structure](#3-structure-around-mutations-to-make-them-predictable)
<!--kg-card-end: html-->
* * *

## It's all well and good to have opinions, but how does Relay enforce them?

Relay is made up of three libraries:

- **Relay compiler** : The ⭐ of the show. Analyzes any GraphQL inside your code during build time, and validates, transforms, and optimizes it for runtime.
- **Relay runtime** : A core runtime that is React-agnostic.
- **React/Relay** : A React integration layer.

You see, if there's anything wrong with your code, the Relay compiler won't pull any punches. It will tell you without hesitation that it simply refuses to build it. Your poor broken code won't even get to run locally, let alone in production.

## What are these conventions you speak of?

Relay not only has a strict GraphQL server specification (which we'll explore in a bit), but also many conventions on the client side. The most famous one of these is **colocation,** where data definitions (what fields we need on a GraphQL type), and view definitions (how to display this data) live happily together inside each component.

## But how do we split the data definitions into separate components?

Do you mean, how do we split them into _fragments_? Why, with GraphQL fragments, of course! **Everything in Relay revolves around fragments**. A fragment is a selection of fields on a GraphQL type.

```
fragment AlbumFragment on Album {
  name
  genre
  tracks {
    name
  }
}

```

Fragments help you write DRY queries.

```
{
  artist(name: "The Muppets") {
    id
    albums {
      image_url
      ...AlbumFragment
    }
    related_artists {
      name
      albums {
        release_date
        ...AlbumFragment
      }
    }
  }
}

```

In Relay, fragments allow you to **colocate data and view definitions.**

```
function AlbumDetail(props) {
  const data = useFragment(graphql`
    fragment AlbumDetail_album on Album {
      genre      
      label
    }
  `, props.album);

  return (
    <Genre>{data.genre}</Genre>
    <Label>{data.label}</Label>
  )
}

```

Relay composes fragments from multiple components into optimized and efficient batches to **reduce round-trips to the server**.

<!--kg-card-begin: html-->

With this structure:

<!--kg-card-end: html-->
- It's hard to over-fetch or under-fetch data, because each component declares exactly the data it needs. **Relay defaults to the performant and defensive pattern.**
- Components can only access data they've asked for. This **data masking** prevents implicit data dependency bugs: each component must declare its own data requirements without relying on others.
- Components only re-render when the exact data they're using is updated, preventing unnecessary re-renders.

In short, colocating data and view definitions makes your components modular, easier to refactor, more performant, and less error-prone.

By the way, did you notice the fragment name, `AlbumDetail_album`? Relay requires globally unique fragment names. The convention is `<module_name>_<property_name>`. The `property_name` part avoids naming collisions when you define multiple fragments in a single module.

Also notice the `useFragment` hook. It takes a fragment definition and fragment reference (`props.album`), and returns the corresponding `data`. The **fragment reference** is the object that contains the fragment in the root query, passed into the component as a prop from the parent component.

> A fragment reference is like a _pointer to a specific instance of a type_ that we want to read data from. - [Relay docs](https://relay.dev/docs/en/experimental/a-guided-tour-of-relay#fragments)

The root query is necessary, because fragments can't be fetched on their own. They must be included ("rooted") in a query inside a parent component. That said, the parent can also be a fragment component, as long as the root exists somewhere up the tree. We'll see the root query in a little bit.

## Sounds tight. But can you really be defensive without strong typing?

Indeed, you can't! That's why Relay is thoroughly type-safe. **Automatic type generation** is only one of the cool things that the Relay compiler does. When you run the compiler, it takes any declared fragments in your code, and generates [Flow](https://flow.org/) types from them ([or TypeScript](https://relay.dev/docs/en/type-emission#language-plugins), if you choose). You can then declare the type for your component's `props` thusly:

```
import type {AlbumDetail_album$key} from './ __generated__ /AlbumDetail_album.graphql';

type Props = {| // Flow exact object type: no additional properties allowed
  album: AlbumDetail_album$key,
|};

function AlbumDetail(props: Props) {
  const data = useFragment(graphql`
    fragment AlbumDetail_album on Album {
      genre      
      label
    }
  `, props.album);

  return (
    <Genre>{data.genre}</Genre>
    <Label>{data.label}</Label>
  )
}

```

Of note here:

- The `$key` suffix denotes a type for the fragment reference, while the `$data` suffix denotes a type for the shape of the data.
- Because our fragment reference (`props.album`) is properly typed, the returning `data` will be automatically Flow typed: `{| genre: ?string, label: ?string} |}`
<!--kg-card-begin: html-->

In addition to **validation** (checking queries for errors and injecting type information), the Relay compiler also applies a bunch of **transforms** to optimize your queries by removing redundancies. This **reduces query payload size** , which leads to... Hey, what was that one thing that Relay was always working towards?... Oh right, better performance! 💃🕺 Here's a neat [REPL](https://relay-compiler-repl.netlify.app) that demos some of these transforms.

<!--kg-card-end: html--><!--kg-card-begin: html-->

Relay also supports [**persisted queries**](https://relay.dev/docs/en/persisted-queries) with the `--persist-output` flag. When enabled, it converts your GraphQL operations into [md5 hashes](https://en.wikipedia.org/wiki/MD5#MD5_hashes). Not only does this reduce query payload size even more, but your server can now **allow-list queries** , meaning clients are restricted to a specific set of queries, improving security.

<!--kg-card-end: html-->
## Back to the root query... What does it look like?

Speaking of being obsessed with performance, Relay offers different ways to fetch and render queries. Let's first look at the less efficient, but more direct way, using the  `useLazyLoadQuery` hook.

```
import React from "react";
import { graphql, useLazyLoadQuery} from "react-relay/hooks";
import type { AlbumQuery } from './ __generated__ /AlbumQuery.graphql';
import AlbumDetail from './AlbumDetail';

function AlbumRoot() {
  const data = useLazyLoadQuery<AlbumQuery>(
    graphql`
      query AlbumQuery($id: ID!) {
        album(id: $id) {
          name

          # Include child fragment:
          ...AlbumDetail_album
        }
      }
    `,
    {id: '4'},
  );

  return (
    <>
      <h1>{data.album?.name}</h1>
      {/* Render child component, passing the fragment reference: */}
      <AlbumDetail album={data.album} />
    </>
  );
}

export default AlbumRoot;

```

We call `useLazyLoadQuery` inside our component (i.e. after our component loads — note this for later), passing in the query and variables, both checked by Flow against the auto-generated `AlbumQuery` type. We include the child fragment `AlbumDetail_album` in the query. The returned `data` is also automatically Flow typed, just like we saw with fragments earlier. Finally, we pass the fragment reference `data.album` into the child component `AlbumDetail`.

> The data obtained as a result of `useLazyLoadQuery` also serves as the fragment reference for any child fragments included in that query. - [Relay docs](https://relay.dev/docs/en/experimental/a-guided-tour-of-relay#queries)

Note that if we were at the very root of our app, we'd wrap everything with a `RelayEnvironmentProvider`, passing via the context an `environment` object, which would be configured with a `network` (fetch function with our GraphQL endpoint) and `store` (the local Relay cache). See examples [here](https://github.com/relayjs/relay-examples/blob/master/issue-tracker/src/index.js) and [here](https://github.com/relayjs/relay-examples/blob/master/issue-tracker/src/RelayEnvironment.js).

## What if I wanted to be less lazy?

If lazy loading queries is not your jam, Relay offers you a way to fetch data more proactively. With `usePreloadedQuery`, we can **start fetching data as soon as there's a user interaction** like hovering over or clicking a link. And with `Suspense`, we can have a fallback UI to show while the data is loading.

```
import React, { Suspense } from "react";
import {
  preloadQuery,
  usePreloadedQuery,
} from "react-relay/hooks";
import type { AlbumQuery } from './ __generated__ /AlbumQuery.graphql';
import RelayEnvironment from './RelayEnvironment';
import AlbumDetail from './AlbumDetail'; 

// Immediately load the query on user interaction. For example,
// we can include this in our routing configuration, preloading
// data as we transition to new routes, e.g. path: '/album/:id'

// See example here: 
// https://github.com/relayjs/relay-examples/blob/master/issue-tracker/src/routes.js

const preloadedQuery = preloadQuery(RelayEnvironment, AlbumQuery, {id: '4'});

function AlbumRoot(props) {

  // Define what data the component needs, with `usePreloadedQuery`.
  // We're not fetching data here; we already fetched it
  // with `preloadQuery` above.
  // The result is passed in via props.
  
  const data = usePreloadedQuery<AlbumQuery>(
    graphql`
      query AlbumQuery($id: ID!) {
        album(id: $id) {
          name
          ...AlbumDetail_album
        }
      }
    `, props.preloadedQuery);

  return (
    <Suspense fallback={"Loading..."}>
      <h1>{data.album?.name}</h1>
      <AlbumDetail album={data.album} />
    </Suspense>
  );
}

export default AlbumRoot;

```

We start fetching data as soon as possible with `preloadQuery`, even before the component loads. Then, inside the component, we render the data as it becomes available, with `usePreloadedQuery`. You can wrap any part of your component with a `Suspense` block to have granular control over what is rendered while you wait for data. This way, you can show some data without waiting for all of it to be ready.

> Suspense will render the provided fallback until all its descendants become "ready" (i.e. until all of the promises thrown inside its subtree of descendants resolve). - [Relay docs](https://relay.dev/docs/en/experimental/a-guided-tour-of-relay#loading-states-with-suspense)

## What if there's an error?

In case of an error, you can wrap your components in an `ErrorBoundary` that you define. See examples [here](https://github.com/relayjs/relay-examples/blob/master/issue-tracker/src/ErrorBoundary.js) and [here](https://github.com/relayjs/relay-examples/blob/c42500546d3ff8dedc8fabc4910e2a54334b6419/issue-tracker/src/routing/RouteRenderer.js#L96). [Error boundaries](https://reactjs.org/docs/error-boundaries.html) were introduced in React 16 as components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI.

Note that unlike Apollo Client, which gives you [error handling options](https://www.apollographql.com/docs/react/data/error-handling/#error-policies), Relay ignores GraphQL errors unless:

- the fetch function provided to the Relay `Network` throws or returns an Error
- the top-level `data` field wasn't returned in the response

To access GraphQL errors in your UI, the recommended practice is to model errors in your GraphQL schema.

```
type Error {
  # User-friendly message
  message: String!
}

type Album {
  image: Result | Error
}

```

See [this article](https://blog.logrocket.com/handling-graphql-errors-like-a-champ-with-unions-and-interfaces/) for an excellent breakdown of how you might model errors this way. And for more on nulls and errors in GraphQL, check out [this cheatsheet](https://hasura.io/blog/graphql-nulls-cheatsheet/).

## How can I access variables in fragments?

Let's say you have an album image that's available in various sizes. In Relay, you can declare local variables for fragments using the `@arguments` and `@argumentDefinitions` directives.

```
# Declare fragment that accepts arguments
fragment AlbumDetail_album on Album
  @argumentDefinitions(scale: { type: "Float!", defaultValue: 2 }) {
  image(scale: $scale) {
    uri
  }
}

# Include fragment and pass in arguments
query AlbumQuery($id: ID!) {
  album(id: $id) {
    name
    ...AlbumDetail_album @arguments(scale: 1)
  }
}
```

☝️ That's a pretty nice superpower! 🦹‍♀️

![](https://hasura.io/blog/content/images/2020/05/superpower.gif)

## I want to write a Relay-compatible GraphQL server

Relay makes [three core assumptions](https://relay.dev/docs/en/graphql-server-specification.html#preface) about a GraphQL server.

### 1. A mechanism for refetching an object

> "The server must provide an interface called `Node`. That interface must include exactly one field, called `id` that returns a non-null ID. This `id` should be a **globally unique identifier** for this object, and given just this `id`, the server should be able to refetch the object." - [GraphQL Docs](https://graphql.org/learn/global-object-identification/#node-interface)

```
interface Node {
  id: ID!
}

```

> The server must provide a root field called `node` that returns the `Node` interface. This root field must take exactly one argument, a non-null ID named `id`. - [GraphQL Docs](https://graphql.org/learn/global-object-identification/#node-root-field)

```
type Album implements Node {
  id: ID!
  name: String!
}

{
  node(id: "4") {
    id
    ... on Album {
      name
    }
  }
}

```

Using the `Node` interface and globally unique `id`s, Relay is able to accomplish amazing feats of performance when it comes to fetching data from the cache or the network. For more on this, check out this [excellent breakdown](https://dev.to/zth/the-magic-of-the-node-interface-4le1) by Gabriel Nordeborn and Sean Grove.

### 2. A description of how to page through connections

Relay uses the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) to standardize pagination. This saves a ton of work on the client side when it comes to managing cursors, merging results, keeping track of loading state, etc, in that you don't need to manually do any of that work. As long as you follow the standard, **Relay takes care of the plumbing** for you behind the scenes.

> In the query, the connection model provides a standard mechanism for slicing and paginating the result set. In the response, the connection model provides a standard way of providing cursors, and a way of telling the client when more results are available. - [Relay spec](https://relay.dev/graphql/connections.htm)

By introducing [layers of indirection](https://graphql.org/learn/pagination/) such as "edges" and "connections," Relay is able to communicate more information between the client and the server in a predictable way.

```
{
  artist {
    name
    albums(first: 2) {
      totalCount
      edges {
        node {
          name
        }
        cursor
      }
      pageInfo {
        endCursor
        hasNextPage
      }
    }
  }
}

```

The connection has info on itself such as `totalCount`, and `pageInfo` such as `endCursor` and `hasNextPage`. The edge has info on the node itself, such as its `cursor`.

For more on pagination in Relay and how it automates everything for you, check out [this article](https://dev.to/zth/pagination-with-minimal-effort-in-relay-gl4) in the same series by Gabriel Nordeborn and Sean Grove.

### 3. Structure around mutations to make them predictable\*

Mutation arguments must be wrapped in an `input` type object.

> By convention, mutations are named as verbs, their inputs are the name with "Input" appended at the end, and they return an object that is the name with "Payload" appended. - [Relay docs](https://relay.dev/docs/en/graphql-server-specification.html#mutations)

\*Although this convention is in the Relay docs, it's not enforced.

```
input AddAlbumInput {
  artistId: ID!
  albumName: String!
}

type AddAlbumPayload {
  artist: Artist
  album: Album
}

mutation AddAlbumMutation($input: AddAlbumInput!) {
  addAlbum(input: $input) {
    album {
      id
      name
    }
    artist {
      name
    }
  }
}
```

<!--kg-card-begin: html-->

🚀With Hasura's [Relay support](https://github.com/hasura/graphql-engine/releases/tag/v1.3.0-beta.1), you get an auto-generated Relay backend, which sets up the above spec for you automatically.

<!--kg-card-end: html-->

## Conclusion

That's it for our whirlwind tour of Relay! We covered:

- How Relay gives you no choice but to build performant, type-safe apps by following strict conventions
- How everything in Relay revolves around fragments & colocation
- How the Relay compiler watches your back with automatic type generation and query validation, and saves you upload bytes with transforms and persisted queries
- Query best practices for even MOAR performant apps
- How to handle loading and error states
- How to pass arguments to fragments
- How to write a Relay-compatible GraphQL server

That's A LOT of new concepts to grok! Cheers for making it to the end 🍻

* * *

When I asked my friends about their experience with Relay, these were their pros & cons:

### Dev Favorites

- [Local state management](https://relay.dev/docs/en/experimental/a-guided-tour-of-relay#local-data-updates)
- Strong typing everywhere
- Excellent tooling
- Data dependency colocation

### Dev Complaints

- "Fragment drilling" for deep trees is gnarly (similar to "[prop drilling](https://kentcdodds.com/blog/prop-drilling)")
- [Testing](https://relay.dev/docs/en/testing-relay-components#fragment-container-tests) is confusing, and generation of [mock data](https://relay.dev/docs/en/testing-relay-components#mock-resolver-context) is weird
- Needs a lot of tooling
- Hard to debug GraphQL

What about you? What has been your experience with Relay? Share with us on [Twitter](https://twitter.com/intent/tweet?text=Hey%2C%20%40sez%20and%20%40HasuraHQ%2C%20regarding%20the%20blog%20post%20https%3A%2F%2Fbit.ly%2F2ZLnQ9m%2C%20) or in the comments below!