The Ultimate Guide to Fetching GraphQL Data in React
GraphQL has taken the web development world by storm since publicly launching in 2015. This open-source data query and manipulation language provides a powerful, flexible alternative to the REST architecture that has dominated web APIs for the past two decades.
A key benefit of GraphQL is enabling clients to specify the exact data they need from an API. Instead of relying on rigid server-defined endpoints, a GraphQL server exposes a schema of its full data model and capabilities. Clients can send queries to a single endpoint, specifying what data to fetch or mutate based on that schema.
This approach offers several major advantages over REST APIs:
- Avoids over-fetching and under-fetching data. With REST, endpoints return fixed data structures, so clients often must request more data than they need or make multiple requests to fetch all requirements. GraphQL queries always return exactly what‘s requested.
- Fewer round trips. Applications can often fetch all required data for a view with a single query instead of making multiple calls to different REST endpoints.
- Strong typing. GraphQL APIs are built around strict schemas using a type system to define possible data. This enables powerful tooling and validation for development.
- Faster iteration. As frontend requirements change, developers can easily adjust their queries without needing to modify or create new API endpoints.
Because of these strengths, GraphQL has been rapidly adopted by major tech companies like Facebook, Twitter, Airbnb, and Shopify as well as countless smaller organizations. The 2020 State of JS survey found that 87.4% of developers who have used GraphQL plan to use it again [^1].
GraphQL in the React Ecosystem
React, Facebook‘s hugely popular JavaScript library for building user interfaces, is often associated with GraphQL. GraphQL was developed internally at Facebook before React was publicly launched, and the two technologies are highly complementary.
In particular, GraphQL enables client-driven data fetching approaches that align well with the component-centric design of React applications. Each React component declares its own data requirements as a GraphQL query and receives exactly the data it needs.
There are several excellent options for integrating GraphQL into React applications. Let‘s take an in-depth look at the most popular libraries and approaches.
Apollo Client
Apollo Client is the most widely used GraphQL client for React and other JavaScript applications. Launched in 2016, it‘s a fully-featured, opinionated platform for using GraphQL on the frontend.
Some of the key features Apollo Client provides include:
- Declarative data fetching and state management via React hooks like
useQuery
anduseMutation
- Automatic caching of GraphQL query responses
- Optimistic UI updates while awaiting server responses
- Prefetching and lazy loading of query results
- Polling and real-time subscriptions for live updates
- Client-side schema utilities for local state management
- Powerful developer tools for debugging and performance monitoring
To use Apollo Client, first install the core @apollo/client
package:
npm install @apollo/client graphql
You‘ll then create an ApolloClient
instance and provide that to an ApolloProvider
component at the root of your application:
import { ApolloClient, InMemoryCache, ApolloProvider } from ‘@apollo/client‘;
const client = new ApolloClient({
uri: ‘https://api.example.com/graphql‘,
cache: new InMemoryCache()
});
function App() {
return (
<ApolloProvider client={client}>
{/* Application components */}
</ApolloProvider>
);
}
With this setup, you can execute queries in any child component using the useQuery
hook:
import { gql, useQuery } from ‘@apollo/client‘;
const GET_ROCKET_INVENTORY = gql`
query GetRocketInventory {
rockets {
id
name
description
flickr_images
}
}
`;
function RocketInventory() {
const { loading, error, data } = useQuery(GET_ROCKET_INVENTORY);
if (loading) return <Loading />;
if (error) return <Error error={error} />;
return (
<div>
<h2>Available Rockets</h2>
<ul>
{data.rockets.map(rocket => (
<li key={rocket.id}>
{rocket.name}
</li>
))}
</ul>
</div>
);
}
Here, the useQuery
hook executes the GET_ROCKET_INVENTORY
query and returns an object with loading, error, and data properties. Apollo Client automatically caches the result and returns it on subsequent executions with the same query and variables.
Alongside useQuery
, Apollo provides a useMutation
hook for modifying data:
const RESERVE_ROCKET = gql`
mutation ReserveRocket($rocketId: ID!) {
reserveRocket(id: $rocketId) {
success
message
tripId
}
}
`;
function ReserveButton({ rocketId }) {
const [reserveRocket, { loading, error, data }] = useMutation(RESERVE_ROCKET);
return (
<button
onClick={() => reserveRocket({ variables: { rocketId } })}
disabled={loading}
>
{loading ? ‘Reserving…‘ : ‘Reserve Rocket‘}
</button>
);
}
Mutations can optionally accept variables and return modified data to update the local Apollo cache.
urql
urql is another popular open-source GraphQL client for React. Compared to Apollo, its aim is to be more modular and lightweight.
Some of the key features and differences of urql include:
- Modular "addon" packages for caching, subscriptions, authentication, etc.
- Customizable cache and exchange pipeline
- Smaller bundle size (~7 kB minified + gzipped)
- Render-as-you-fetch approach for improved perceived performance
Getting started with urql is similar to Apollo. Install the urql
package:
npm install urql graphql
Create a client instance and provider:
import { createClient, Provider } from ‘urql‘;
const client = createClient({
url: ‘https://api.example.com/graphql‘,
});
ReactDOM.render(
<Provider value={client}>
<App />
</Provider>,
document.getElementById(‘root‘)
);
And execute queries with the useQuery
hook:
import { useQuery } from ‘urql‘;
const ROCKET_INVENTORY_QUERY = `
query {
rockets {
id
name
description
flickr_images
}
}
`;
function RocketInventory() {
const [result, reexecuteQuery] = useQuery({
query: ROCKET_INVENTORY_QUERY,
});
const { data, fetching, error } = result;
if (fetching) return <Loading />;
if (error) return <Error error={error} />;
return (
<div>
<h2>Available Rockets</h2>
<ul>
{data.rockets.map(rocket => (
<li key={rocket.id}>
{rocket.name}
</li>
))}
</ul>
</div>
);
}
One difference is that urql‘s useQuery
returns a tuple with the query result object and a reexecuteQuery
function to manually refetch data.
Relay
Relay is Facebook‘s own GraphQL client for React applications. It‘s highly opinionated and optimized around performance and scalability for complex applications.
Some standout features of Relay include:
- Build-time optimizations with ahead-of-time compilation of queries
- Aggressive local data caching and optimized network utilization
- Fine-grained subscriptions to data changes
- Support for local GraphQL schema extensions
- Framework integrations (e.g. React Native, Next.js)
Using Relay requires adding the Relay compiler to your build process and precompiling GraphQL queries:
// RocketInventory.js
import { graphql, usePreloadedQuery } from ‘react-relay‘;
const RocketInventoryQuery = graphql`
query RocketInventoryQuery {
rockets {
id
name
description
flickr_images
}
}
`;
function RocketInventory({ preloadedQuery }) {
const data = usePreloadedQuery(RocketInventoryQuery, preloadedQuery);
return (
<div>
<h2>Available Rockets</h2>
<ul>
{data.rockets.map(rocket => (
<li key={rocket.id}>
{rocket.name}
</li>
))}
</ul>
</div>
);
}
While powerful, Relay has a steeper learning curve and may be overkill for simpler applications compared to Apollo or urql.
Lightweight Alternatives
For applications that don‘t require all the features of the above libraries, lightweight alternatives using React Query or SWR to manage data fetching can be a good choice.
React Query is a library for handling asynchronous state and caching in React applications. While not GraphQL-specific, it works well with minimal GraphQL clients like graphql-request.
import { useQuery } from ‘react-query‘;
import { request, gql } from ‘graphql-request‘;
const endpoint = ‘https://api.example.com/graphql‘;
const ROCKET_INVENTORY_QUERY = gql`
query RocketInventory {
rockets {
id
name
description
flickr_images
}
}
`;
function RocketInventory() {
const { isLoading, error, data } = useQuery(‘rocketInventory‘, () =>
request(endpoint, ROCKET_INVENTORY_QUERY)
);
if (isLoading) return <Loading />;
if (error) return <Error error={error} />;
return (
<div>
<h2>Available Rockets</h2>
<ul>
{data.rockets.map(rocket => (
<li key={rocket.id}>
{rocket.name}
</li>
))}
</ul>
</div>
);
}
This approach foregoes some of the automatic caching and state management of a full GraphQL client but can be a good choice for applications with simple data requirements.
Testing and Monitoring GraphQL APIs
As with any API, testing and monitoring GraphQL endpoints is crucial to maintaining a high-quality, reliable application. Some key considerations and tooling options include:
- Schema change validation: Tools like Apollo Studio can detect changes to a GraphQL schema that may break existing client queries
- Automated testing: End-to-end testing tools like Cypress can be used to execute and validate GraphQL queries
- Monitoring and analytics: Platforms like Apollo Graph Manager provide monitoring, performance tracing, and usage analytics for GraphQL APIs
By incorporating testing and monitoring into their workflows, teams can confidently iterate on GraphQL APIs and maintain application health as they scale.
GraphQL at Scale
GraphQL is increasingly being adopted by large organizations to power mission-critical applications. Some notable examples include:
- GitHub used GraphQL to redesign their API and has since made it the primary means of interacting programmatically with their platform
- Airbnb transitioned large parts of their architecture to GraphQL and saw major improvements in developer productivity and application performance
- The New York Times uses Apollo Client and Server to power their website and native applications
Some common approaches these organizations employ include:
- Federated architectures with multiple GraphQL services stitched together into a single schema
- Using GraphQL as an abstraction layer over legacy services and databases
- Optimizing client-side caching and state management with libraries like Apollo and Relay
The GraphQL specification and surrounding tooling ecosystem has matured to support these kinds of large-scale deployments.
The Future of GraphQL
Looking ahead, there are a number of exciting developments in the GraphQL community:
- Ongoing work on the GraphQL specification to add new features like interfaces, annotations, and custom directives
- Performance improvements in client libraries and server runtimes
- Enhanced type safety with code generation and typed clients
- Adoption of GraphQL for real-time use cases beyond request/response data fetching
As the ecosystem continues to mature and evolve, GraphQL is well-positioned to become the default choice for building modern, data-driven applications with React and beyond.
Conclusion
GraphQL provides a powerful, flexible approach to fetch data for React applications. By enabling clients to query for exactly the data they need, GraphQL can simplify data fetching code, improve performance, and enable rapid development as requirements change.
This guide covered the following major approaches to fetching GraphQL data in React:
- Using a fully-featured client like Apollo or Relay
- Using a modular client like urql
- Combining a lightweight client with React Query
It also explored key considerations and best practices around testing, monitoring, and scaling GraphQL APIs.
Ultimately, the choice of how to integrate GraphQL into a React application depends on factors like performance requirements, team expertise, and anticipated growth. But with its strong typing, client-driven architecture, and vibrant ecosystem, GraphQL is a compelling choice for any modern web application.