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
Alright, having our project created, let's install the necessary dependencies:
pnpm add react-relay relay-runtime
And also the development dependencies:
pnpm add -D relay-compiler vite-plugin-relay graphql get-graphql-schema @types/react-relay @types/relay-runtime
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
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
}
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],
})
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 });
}
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>
);
}
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;
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>
);
}
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
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
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>
);
}
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>
);
now, just run the dev script and see the list of characters
pnpm dev
accessing localhost:5173 (vite's default)
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 👋
Top comments (3)
Nice write-up! It's comprehensible even for someone who is entirely focused on backend development, like myself. Congratulations
amazing tutorial!
can’t wait to try relay myself!
Great job on this article, Mauricio!
The config reminds me a lot of how React Query from Tanstack works, especially because of how we need to configure the query provider, which is essentially an abstraction of what Relay requires doing manually.
And that query part with the auto-generated types... chef's kiss. I like having a well-organized code and I can already imagine how clean can we make each query and each component.