Exploring GraphQL Clients: Apollo Client vs Relay vs URQL
So, you’ve finally decided to build that GraphQL project. You’ve chosen your programming language, database, and Hasura is powering your GraphQL APIs, but what GraphQL client do you use on the front end? One of the most common challenges front-end developers face when starting a new GraphQL project is choosing which GraphQL client to use. With more than 12 JavaScript client libraries listed on GraphQL.org, in addition to Fluent GraphQL clients that we have been exploring in Hasura, choosing the right GraphQL client for your unique needs might seem challenging.
Apollo Client, Relay, and URQL are some of the most popular GraphQL client libraries based on GitHub stars and npm downloads. With these GraphQL clients, you get a fast, robust, scalable, and efficient way of querying and managing GraphQL APIs without having to worry about lower-level networking details or maintaining a local cache.
Whether you are looking for the right GraphQL client for your next project or your current GraphQL client is not meeting your demands, one of these top three GraphQL clients can help you move in a better direction. Here, we’ll be exploring Apollo Client, Relay, and URQL, as well as each of their pros and cons, to help you make the right choice when selecting a GraphQL client for your future projects.
What Is Apollo Client?
Apollo Client is a fully-featured, comprehensive state management GraphQL client that enables you to manage local and remote data with GraphQL. It was released in 2016 by the Meteor Development Group shortly after the GraphQL project was open-sourced with the ambition to build a GraphQL client library for every front-end web framework.
Querying data with Apollo Client
Let’s walk through a simple example of querying GraphQL data using Apollo Client in a React application.
First, install the Apollo Client library from within your React application.
npm install --save @apollo/client graphql
Once you’ve installed Apollo Client, you will need to import the following modules to make a basic query, as shown below.
//index.js
import {
ApolloClient,
InMemoryCache,
gql
} from "@apollo/client";
Next, we initialize an instance of Apollo Client and pass in the URI and cache parameters in the constructor.
//index.js
const client = new ApolloClient({
uri: 'https://api.spacex.land/graphql/',
cache: new InMemoryCache()
});
- The URI specifies the GraphQL endpoint you want to retrieve data from. (We are using SpaceX's public GraphQL API for this example.)
- The cache is an instance of InMemoryCache, which allows you to cache query results and configure Apollo Client's caching strategies.
We can then specify our query data by using the client.query() method with the query string, as shown below.
//index.js
client.query({
query: gql`
{
missions {
name
website
}
}
`,
}).then(result => console.log(result));
Run your code and inspect the network tab to check the result object. You should see a data property along with the mission's data.
This example shows a basic way of querying data with Apollo Client. However, Apollo Client offers many other features especially geared toward React applications, such as direct integration with React Hooks.
To explore Apollo Client further, including queries, mutations, and subscriptions, check out this Apollo Client tutorial on our learning platform. We build a to-do app using React, Apollo Client, and Hasura.
Pros of Apollo Client
- Apollo Client doubles as a caching and state management library: Apollo Client ships with a built-in caching and state management feature to help you manage local state, irrespective of whether you have a GraphQL API to connect to. Additionally, you can cache the state you receive the Remote API in and combine it with the local state, saving network bandwidth and time when dealing with data.
- Apollo Client ships with many built-in features: You get features such as error handling, pagination, data prefetching, and integration with React Hooks built-in. Having these features can accelerate development time and boost productivity.
- Apollo Client supports many frameworks: Apollo Client supports React, Angular, Vue, Ember, iOS, and Android—all of which can be advantageous if you use GraphQL in multiple projects as it saves you time and effort in switching between GraphQL projects.
- Apollo Client 3.0 features reactive variables: Reactive variables can store data of any type and can be accessed anywhere in the application. Reactive variables can be an advantageous mechanism for representing local states outside the Apollo Client cache.
When does Apollo Client not fit in?
- Large bundle size: One of the biggest downsides to adopting Apollo is the bundle size. The smallest version of Apollo which makes it usable, comes in at 30.7KB, twice the size of other GraphQL clients like URQL. This large bundle size could lead to poor app performance in bandwidth-constrained applications.
- Lack of extensive support for other front-end frameworks aside from React: Apollo Client was built with React in mind and ships with many custom features for React applications. Often, developers have to rely on unofficial third-party tooling for other front-end frameworks, which can be buggy and bloated.
What Is Relay?
Relay is a highly opinionated GraphQL client for managing and fetching data from GraphQL APIs in React applications. It was created by Facebook and first released in 2015. Relay Modern was released in 2017 as a more straightforward, optimized, and extensible version of the original Relay.
Querying data with Relay
Let’s walk through a simple example of querying GraphQL data using Relay in a React application. We first need to install the necessary packages. Relay is comprised of three main pieces:
- A compiler (which is used at build time)
- A core runtime (that is React-agnostic)
- A React integration layer
Install the three packages from npm using the following commands:
npm install --save relay-runtime react-relay
npm install --save-dev relay-compiler graphql@^15.0.0 babel-plugin-relay
react-relay: This contains the main functionality of the Relay runtime and is responsible for caching and networking tasks.
relay-compiler: The Relay Compiler validates and optimizes the GraphQL code you develop during build time.
babel-plugin-relay: Relay uses a Babel plugin to convert the GraphQL code you write in a project into the format required by the Relay Compiler.
Configure the Relay Compiler
We need to configure the Relay Compiler to link to the GraphQL server schema. To do this, we download the GraphQL schema from the GraphQL server and store it in a .graphql file. Luckily, we created a graphqurl tool that can help with this.
From your terminal, run the following commands:
npm install -g graphqurl
gq <uri_endpoint> --introspect > schema.graphql
The graphqurl tool downloads the GraphQL schema from our URI endpoint (https://api.spacex.land/graphql/) and stores it in a schema.graphql file.
Now that we have the GraphQL schema, we can modify the package.json to run the compiler whenever we build or start our app:
// <your-app-name>/package.json
{
...
"scripts": {
...
"start": "npm run relay && react-scripts start",
"build": "npm run relay && react-scripts build",
"relay": "npm run relay-compiler --schema schema.graphql --src ./src/ --watchman false $@"
...
},
...
}
Configure the Relay runtime and environment
Now that our compiler has been configured, we can set up the runtime to tell Relay how to connect to our GraphQL server.
Inside the src folder, create a RelayEnvironment.js file and paste the following code.
// <your-app-name>/src/RelayEnvironment.js
import { Environment, Network, RecordSource, Store } from "relay-runtime";
const store = new Store(new RecordSource());
const network = Network.create((operation, variables) => {
return fetch("https://api.spacex.land/graphql/", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
query: operation.text,
variables
})
}).then(response => {
return response.json();
});
});
export default new Environment({
network,
store
});
In the above code snippet, we import the required modules Environment, Network, RecordSource, and Store from Relay runtime. We then create an instance of Store, which will hold the cached data. The next thing we do is set up a network interface using the fetch client to call our GraphQL API. Finally, we export our Relay environment to use it in other modules.
Now that our environment has been configured, let's render a query to get data. Open your App.js file and paste in the following code snippet.
//App.js
import React from "react";
import { QueryRenderer, graphql } from "react-relay";
import environment from './RelayEnvironment';
const query = graphql`
query AppRepositoryMissionQuery{
missions {
name
website
}
}
`;
export default function Root() {
return (
<QueryRenderer
environment={environment}
query={query}
render={({ error, props }) => {
if (error) {
return <p>{error.message}</p>;
} else if (props) {
return <p>Mission name is {props.name} with website {props.website}</p>;
}
return <p>Loading your Relay Mordern data...</p>;
}}
/>
);
}
Here we imported the RelayEnvironment we configured earlier, as well as the QueryRenderer and graphql from react-relay. We then defined the data we wanted to receive from our GraphQL server. Finally, we passed the environment andthe query to the QueryRenderer. The render function receives the result from the GraphQL server and passes it to the HTML elements to display.
Start your application and inspect the networking tab to check the data that was sent back by the GraphQL server.
As you might have already realized, setting up and configuring Relay was not as straightforward as Apollo Client, but on the bright side, you only have to do the initial setup once. To learn more about Relay features such as Fragments, you can refer to the official documentation.
What works for Relay
- Relay is designed for high performance and automatically scales as your app grows: Relay is heavily focused on application performance. Its incremental compiler keeps iteration speed fast as the application grows and optimizes queries ahead of time to make the runtime as fast and efficient as possible.
- Relay applies and relies on GraphQL best practices: Relay ensures GraphQL APIs are built according to their predefined schema format. For example, Relay relies on globally unique IDs across the entire schema type to provide reliable caching and data prefetching. This enforcement of strong patterns ensures API consistency.
- Relay supports declarative data fetching: With Relay, you can declare your data requirements and let Relay figure out how and when to fetch your data. This feature reduces the time and complexity required to consume GraphQL APIs in React applications.
- Relay reduces the probability of failure in front-end applications: Relay supports type safety and automatic schema consistency and forces developers to follow a strict convention. These features ensure a clean architecture and minimize breaking changes that could cause application failure.
What doesn't work for Relay
- Relay can be complex to set up and has a large bundle size: Because Relay is highly opinionated and conforms to a strict standard, the GraphQL API Relay connects to has to be built with a particular convention, which means less flexibility in GraphQL API design. Additionally, setting up Relay can be bulky and complex. It has a large bundle size, and you need to add a custom configuration when setting it up. This complexity, bulkiness, and lack of flexibility can be disadvantageous, especially when your team needs a quick, straightforward GraphQL client option.
- Relay can only be used with React and React Native applications: Relay only supports React and React Native applications. If your team uses other front-end languages aside from React, you will need a different GraphQL client to support it, leading to more time and effort learning about a new GraphQL client.
What Is URQL?
URQL, which stands for Universal React Query Library, is a lightweight, highly customizable, extensible GraphQL client that exposes a set of helpers for several frameworks, including React, Svelte, and Vue. It was released in 2018 by Formidable Labs as a lightweight, minimalist alternative to GraphQL clients such as Apollo and Relay.
Querying data with URQL
Let’s walk through a simple example of querying GraphQL data using URQL in a React application.
First, install the URQL library from within your React application.
npm install --save urql graphql
Once you’ve installed URQL, you will need to import the following method to create a basic GraphQL client, as shown below.
//index.js
import { createClient } from 'urql';
const client = createClient({
url: 'https://api.spacex.land/graphql/',
});
Here, we pass in the GraphQL URL in the createClient constructor (we are still making use of the SpaceX public GraphQL API).
We then specify our query data by passing the query string to the client.query() method, as shown below.
//index.js
const query = `
{
missions {
name
website
}
}`;
const results = await client.query(query).toPromise();
console.log(result);
Run your code and inspect the network tab to check the result object. You should see a data property along with the mission’s data.
As you can see, URQL is relatively easy to set up and integrate; as with the Apollo and Relay, there are still so many features URQL has to offer. You can refer to the URQL documentation to learn more.
Pros of URQL
- URQL is lightweight and easy to set up: URQL ships with a bundle size of just 12KB, which is incredibly lightweight, especially compared to other GraphQL clients like Apollo. URQL is also the easiest to set up among the three GraphQL clients, as shown in the example above. These two features are advantageous when building lightweight, performant applications.
- URQL is flexible and extensible: URQL provides an extensibility API that allows app developers to customize the URQL GraphQL library to suit their needs. This extensibility and flexibility allow developers to build custom implementations on top of URQL and tweak them to suit their custom scenarios.
- Caching in URQL is fully customizable through its exchanges. URQL supports document caching for content-heavy pages and normalized caching for dynamic and data-heavy applications. This flexibility allows developers to easily set up caching on your client applications based on your specific requirements.
- URQL provides first-class support for functionality, such as offline mode and file uploads. URQL has built-in support for additional functionality, such as offline mode. This first-class support means you spend less time implementing features such as offline mode and file uploads in your client application.
What can be better for URQL?
- A relatively new and small community when compared to other GraphQL clients: URQL was released in 2018 and currently has 157 contributors on GitHub. However, compared to Relay, which was released in 2015 and has 492 contributors, and Apollo, which was released in 2016 and has 644 contributors, it is noticeably smaller. URQL is relatively new, and its community is not as vibrant and mature as the Relay and Apollo communities.
- Lack of some built-in features: URQL lacks first-class support for features such as query batching, local state management, and pagination, which ship out of the box with Apollo and Relay. This means developers spend more time manually implementing these features in their applications.
Apollo vs. Relay vs. URQL: A Quick Overview
There are a couple of features all three clients support, such as caching, queries, mutations, and subscriptions. However, their implementations of these features differ. For example, both Apollo and Relay support normalized caching, while URQL supports document and normalized caching.
The most fundamental difference between the three clients is in their core philosophy. Apollo is flexible and easygoing, Relay is opinionated and structured, and URQL is lightweight and extensible.
This table provides a quick overview of the three GraphQL clients, along with some of their noteworthy features.
For a complete overview of all their core features, including framework bindings, caching, and state, refer to this article on the URQL page.
Apollo vs. Relay vs. URQL: Which of These GraphQL Clients Is Right for Your Needs?
Choosing the right GraphQL client will ultimately depend on your tech stack, the size and maturity of your engineering team, and your application's data requirements. Going through the features, advantages, and disadvantages of each GraphQL client and analyzing it for your specific use case is a good way to make a choice.
Apollo is good for teams with multiple development environments looking for a fully-featured “all-rounder” GraphQL client solution.
Relay is suitable for large-scale applications with complex data requirements and numerous dependencies between different application parts, where manually managing these dependencies would be highly time-consuming and error-prone.
URQL is a good option for bandwidth-constrained applications and teams that require a lightweight, flexible, and easy-to-use GraphQL client approach.
Whichever solution you choose, Hasura can power your GraphQL APIs. Hasura's GraphQL engine gives you real-time, instant GraphQL APIs over your database. Hasura makes it easy to build your back-end GraphQL app or add a GraphQL layer to your existing application. Sign up for free today!