Facing the Monster: An Analgesic for Relayphobia

mauvieira

Mauricio Vieira

Posted on April 8, 2024

Facing the Monster: An Analgesic for Relayphobia

In all these years of building interfaces with React, I used Apollo whenever I needed to consume an API made with GraphQL, mainly because it's a library with a large community and numerous example projects

However, I recently joined the fantastic team at Coinbase and met a dude I'd always heard people speak very poorly of: The Frightening and Feared Relay

Initially, I was really confused with this thing, having never worked with Relay before. Still, in a very short time, I was completely thrilled by the incredibly super fantastic DX this guy brings. I can make a powerful statement: undoubtedly, it's one of the best tools for productivity within the React ecosystem.

This raised a question for me: Why on earth do people speak so poorly of Relay? And why is its use much lower than other solutions for consuming GraphQL like Apollo?

Well, after discussing with some people, the reasons I heard the most were:

  • Very confusing documentation
  • Small community
  • A few examples with TypeScript
  • Very difficult to start using

My friend Thayto mentioned this last point a few days ago. He wanted to start a project to explore Relay but found the documentation too confusing and overly theoretical. He wanted to start coding and learn along the way, but the amount of configuration to do and the very few guides available made him give up...

I admit that I was really sad to hear this. I wanted others to feel what I feel every day working with Relay

With that in mind, I decided to create a painkiller for all this Relayphobia: an extremely simple and detailed tutorial that can serve as a starting point for anyone who wants to learn Relay

My goal here is not to teach Relay per se, but to demystify the early stages of engagement with the tool and provide you the confidence needed to navigate and utilize the official docs more effectively

Alright, let's get started

Starting the Project

I will use a Vite template with React and TypeScript as the base for the project.

I am using pnpm here, but it's not a requirement; you can use yarn or another package manager.

pnpm create vite
Enter fullscreen mode Exit fullscreen mode

Alright, having our project created, let's install the necessary dependencies:


pnpm add react-relay relay-runtime

Enter fullscreen mode Exit fullscreen mode

And also the development dependencies:


pnpm add -D relay-compiler vite-plugin-relay graphql get-graphql-schema @types/react-relay @types/relay-runtime

Enter fullscreen mode Exit fullscreen mode

I don't plan to go too deep into what each of these dependencies is, but to give a brief introduction:

  • react-relay: This is basically the bridge between React and Relay, allowing React components to use data through Relay. 
  • relay-runtime: This is the central core, providing the necessary infrastructure for Relay to function. It manages storage, data retrieval, and caching. 
  • relay-compiler: This is where the magic happens! One of the most crucial and significant tools, it compiles the GraphQL queries used in the project in a very performant way. It ensures that these queries are optimized and converted into efficient formats. 
  • get-graphql-schema: This CLI tool allows you to generate a GraphQL schema from a URL. It is an extremely necessary step.

Schema and the Essential Configuration

Relay can work with any GraphQL API, but it's optimized for APIs that follow certain conventions and standards. However, we don't need to worry about that for this guide

I've decided to use the API from Rick and Morty because it's good and fun

With the URL in hand, we can generate the file that will be used as the schema:


pnpm get-graphql-schema https://rickandmortyapi.com/graphql > schema.graphql

Enter fullscreen mode Exit fullscreen mode

With our schema created, we can create our configuration file at the root of the project:

// relay.config.json

{
  "src": "./src",
  "schema": "./schema.graphql",
  "language": "typescript",
  "exclude": [
    "**/node_modules/**",
    "**/__mocks__/**",
    "**/__generated__/**"
  ],
  "eagerEsModules": true
}
Enter fullscreen mode Exit fullscreen mode

We also need to add a plugin to our Vite configuration:

// vite.config.ts

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import relay from "vite-plugin-relay";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), relay],
})
Enter fullscreen mode Exit fullscreen mode

Creating the Environment

IT'S ALMOST OVER, I PROMISE!!!!!

This is where it gets a bit complex. I confess that the first time I picked up Relay to study, I simply copied these files without even looking inside…

Basically, we are going to create a folder named relay with two files inside: environment.ts and RelayEnvironment.tsx:

// /src/relay/environment.ts

import {
  Store,
  RecordSource,
  Environment,
  Network,
  Observable,
} from "relay-runtime";
import type { FetchFunction, IEnvironment } from "relay-runtime";

const fetchFn: FetchFunction = (params, variables) => {
  const response = fetch("https://rickandmortyapi.com/graphql/", {
    method: "POST",
    headers: [["Content-Type", "application/json"]],
    body: JSON.stringify({
      query: params.text,
      variables,
    }),
  });

  return Observable.from(response.then((data) => data.json()));
};

export function createEnvironment(): IEnvironment {
  const network = Network.create(fetchFn);
  const store = new Store(new RecordSource());
  return new Environment({ store, network });
}
Enter fullscreen mode Exit fullscreen mode

This guy is a bit confusing; let's go piece by piece:

  • Fetch Function (fetchFn): This is the simplest part. It's responsible for making POST requests to our GraphQL API.
  • Environment: This class contains the central configuration of Relay. It brings together various configurations and provides the infrastructure for Relay operations.
  • Store and RecordSource: The Store in Relay stores the fetched data. It acts like a client-side database. The RecordSource, on the other hand, is an in-memory storage. It's part of the Store that actually holds the data.
// /src/relay/RelayEnvironment.tsx

import * as React from "react";
import { useMemo } from "react";
import { RelayEnvironmentProvider } from "react-relay";
import { createEnvironment } from "./environment";

export default function RelayEnvironment({
  children,
}: {
  children: React.ReactNode;
}): React.ReactElement {
  const environment = useMemo(() => {
    return createEnvironment();
  }, []);

  return (
    <RelayEnvironmentProvider environment={environment}>
      {children}
    </RelayEnvironmentProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

In summary, environment.ts defines how data is fetched and managed, while RelayEnvironment.tsx provides the configured environment for our app

Adding the environment to the root of our app:

// src/App.tsx

import { Suspense } from "react";
import RelayEnvironment from "./relay/RelayEnvironment";

function App() {
  return (
    <RelayEnvironment>
      <ErrorBoundary fallback={<h1>Something went wrong.</h1>}>
        <Suspense fallback={<p>loading</p>}>
          <h1>app</h1>
        </Suspense>
      </ErrorBoundary>
    </RelayEnvironment>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Now, enough with the configurations, for God's sake…

Creating our first Query

Let's create a file named HomePage.tsx, which will be our main page and display a list of characters from the show

import { useLazyLoadQuery, graphql } from "react-relay";

const homeQuery = graphql`
  query HomePageQuery {
    characters {
      results {
        id
        name
        species
        image
      }
    }
  }
`;

export default function Home() {
  const data = useLazyLoadQuery(homeQuery, {});

  return (
    <div>home</div>
  );
}


Enter fullscreen mode Exit fullscreen mode

Here, we are essentially creating a query and calling it with the useLazyLoadQuery hook

However, if you check the type of our data variable, it will be unknown

This is where the magic happens. Let's put our compiler to work!! 🧙🪄


pnpm relay-compiler

Enter fullscreen mode Exit fullscreen mode

Based on our schema, the compiler will see that we are using a query and automatically generate a file containing all the types we need

FileTree showing the generated file

I use a light theme btw

now, we can import the generated type and use it as a generic in the hook call:

import { useLazyLoadQuery, graphql } from "react-relay";
import { HomePageQuery } from "./__generated__/HomePageQuery.graphql";

const homeQuery = graphql`
`;

export default function Home() {
  const data = useLazyLoadQuery<HomePageQuery>(homeQuery, {});

  // data.characters?.results;

  return (
    <div>home</div>
  );
}
Enter fullscreen mode Exit fullscreen mode

now our response is typed, magical, isn't it?????

Let's display this data on the screen:

  return (
    <ul>
      {characters?.map(
        (character) =>
          character && (
            <li key={character.id}>
              <p>{character.name}</p>
              {character.image && (
                <img src={character.image} alt={`Image of ${character.name}`} />
              )}
            </li>
          )
      )}
    </ul>
  );
Enter fullscreen mode Exit fullscreen mode

now, just run the dev script and see the list of characters


pnpm dev

Enter fullscreen mode Exit fullscreen mode

accessing localhost:5173 (vite's default)

Screenshot of the project running

and that's it!!!!!! Relay configured and running 😎🤘

here's the full code

Next Steps

As I mentioned in the beginning, this is just a guide to help you get started with Relay. I haven't covered API definitions or best practices - now it's up to you.

I'll leave you with a challenge: Try to understand what fragments are. In my opinion, this is the most fun part of Relay.

If you have any questions, feedbacks or simply want to chat, you can reach out to me on twitter =)

see ya, take care 👋

💖 💪 🙅 🚩
mauvieira
Mauricio Vieira

Posted on April 8, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related