apollo-client and react-apollo are legacy packages that have been unified into the modern @apollo/client library, providing a comprehensive state management and data fetching solution for GraphQL. graphql-request is a minimal, flexible client focused on simple HTTP requests without built-in caching or React bindings. urql is a highly customizable GraphQL client built by Formidable, offering a balanced approach between features and bundle size with a modular exchange system. Together, these tools represent the spectrum from full-featured frameworks to lightweight utilities for handling GraphQL in JavaScript applications.
Choosing the right GraphQL client impacts how your application handles data fetching, caching, and state management. The landscape includes legacy tools that shaped the ecosystem and modern alternatives designed for simplicity and speed. Let's compare apollo-client, react-apollo, graphql-request, and urql to help you make an informed architectural decision.
Before writing code, you must know the maintenance status of these packages. Two of the four options are deprecated and should not be used in new projects.
apollo-client is the legacy core package. It has been replaced by @apollo/client.
// apollo-client (Legacy - DO NOT USE)
import ApolloClient from 'apollo-client';
const client = new ApolloClient({ uri: '/graphql' });
react-apollo is the legacy React binding. It is merged into @apollo/client.
// react-apollo (Legacy - DO NOT USE)
import { useQuery } from 'react-apollo';
const { data } = useQuery(MY_QUERY);
graphql-request is actively maintained and stable for minimal use cases.
// graphql-request (Current)
import { request } from 'graphql-request';
const data = await request(endpoint, query);
urql is actively maintained and recommended for modern React apps.
// urql (Current)
import { createClient } from '@urql/core';
const client = createClient({ url: '/graphql' });
Setting up the client determines how much boilerplate you write initially. Apollo requires the most configuration, while graphql-request requires almost none.
apollo-client (Legacy) needed separate setup for the client and React provider.
// apollo-client (Legacy)
import ApolloClient from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
const client = new ApolloClient({ cache: new InMemoryCache(), uri: '/graphql' });
react-apollo (Legacy) wrapped the app in a provider to access hooks.
// react-apollo (Legacy)
import { ApolloProvider } from 'react-apollo';
ReactDOM.render(<ApolloProvider client={client}><App /></ApolloProvider>, root);
graphql-request does not require a provider or complex setup.
// graphql-request
import { GraphQLClient } from 'graphql-request';
const client = new GraphQLClient('/graphql', { headers: { ... } });
urql uses a client instance passed via a provider, similar to modern Apollo.
// urql
import { Provider, createClient } from 'urql';
const client = createClient({ url: '/graphql' });
ReactDOM.render(<Provider value={client}><App /></Provider>, root);
How you fetch data in components defines your developer experience. Hooks are standard now, but some libraries still rely on function calls.
apollo-client (Legacy) used hooks from the separate react-apollo package.
// apollo-client + react-apollo (Legacy)
import { useQuery } from 'react-apollo';
function User() {
const { data } = useQuery(USER_QUERY);
return <div>{data?.user.name}</div>;
}
react-apollo (Legacy) was the specific package providing these hooks.
// react-apollo (Legacy)
import { useMutation } from 'react-apollo';
const [login] = useMutation(LOGIN_MUTATION);
graphql-request relies on standard fetch patterns inside effects or handlers.
// graphql-request
import { request } from 'graphql-request';
function User() {
const [data, setData] = useState(null);
useEffect(() => { request('/graphql', USER_QUERY).then(setData); }, []);
return <div>{data?.user.name}</div>;
}
urql provides built-in hooks that handle loading and error states automatically.
// urql
import { useQuery } from 'urql';
function User() {
const [result] = useQuery({ query: USER_QUERY });
return <div>{result.data?.user.name}</div>;
}
Caching reduces network requests and improves UI speed. This is where the differences become stark.
apollo-client (Legacy) included a powerful normalized cache out of the box.
// apollo-client (Legacy)
import { InMemoryCache } from 'apollo-cache-inmemory';
const cache = new InMemoryCache();
// Automatically normalizes data by ID
react-apollo (Legacy) connected components to this cache.
// react-apollo (Legacy)
// Components re-render automatically when cache data changes
graphql-request has no built-in caching. You must implement it yourself.
// graphql-request
// No cache. Every call hits the network.
const data = await request('/graphql', QUERY);
// You must store 'data' in React state or localStorage manually
urql includes a document cache by default, with optional normalized caching.
// urql
import { cacheExchange } from '@urql/core';
const client = createClient({ exchanges: [cacheExchange, ...] });
// Caches queries by default; add graphcache for normalization
Handling failures gracefully is critical for production apps. Each library exposes errors differently.
apollo-client (Legacy) returned errors in the hook result object.
// apollo-client + react-apollo (Legacy)
const { error } = useQuery(USER_QUERY);
if (error) return <p>Failed to load</p>;
react-apollo (Legacy) provided the error boundary context.
// react-apollo (Legacy)
import { ApolloError } from 'apollo-client';
// Manual checking of error types required
graphql-request throws errors that you must catch manually.
// graphql-request
try {
await request('/graphql', QUERY);
} catch (error) {
console.error(error.response.errors);
}
urql includes error state directly in the result tuple.
// urql
const [result] = useQuery({ query: USER_QUERY });
if (result.error) return <p>Failed to load</p>;
| Feature | apollo-client / react-apollo | graphql-request | urql |
|---|---|---|---|
| Status | ā Deprecated (Use @apollo/client) | ā Active | ā Active |
| Bundle Size | š Large | š¦ Tiny | š„ Small |
| Caching | š Normalized (Built-in) | ā None | š Document (Built-in) |
| React Hooks | ā Yes (Legacy) | ā No (Manual) | ā Yes |
| Complexity | š High | š Low | š Medium |
apollo-client and react-apollo are the foundation of the old GraphQL ecosystem. They are powerful but now obsolete. If you need Apollo's features, use @apollo/client. It is best for enterprise apps needing complex state sync.
graphql-request is the lightweight choice. It works well for scripts, server-side code, or simple apps where you do not need caching. You trade convenience for control and size.
urql is the modern middle ground. It offers hooks and caching without the heaviness of Apollo. It is ideal for teams wanting a clean API and room to customize via exchanges.
Final Thought: Avoid apollo-client and react-apollo in new work. Choose urql for balanced React integration or graphql-request for minimal needs. If you require the full Apollo ecosystem, migrate to @apollo/client immediately.
Avoid using the legacy apollo-client package in new projects as it is deprecated. Instead, choose the modern @apollo/client which unifies the core client and React bindings. This path is best for large-scale applications requiring robust caching, optimistic UI updates, and complex state management alongside GraphQL data.
Choose graphql-request if you need a lightweight, framework-agnostic solution for simple queries without the overhead of a full client. It is ideal for server-side scripts, Next.js API routes, or React apps where you prefer to manage caching and state manually or do not need advanced features like optimistic updates.
Do not use react-apollo for new development as it is officially deprecated and no longer maintained. Its features have been merged into @apollo/client. Selecting this package introduces security risks and lack of support, so migrate to the unified Apollo client or consider alternatives like urql.
Choose urql if you want a modern, modular client that balances features with performance. It is suitable for teams that need customization via exchanges without the complexity of Apollo, offering strong TypeScript support and a simpler API for standard data fetching and caching needs.
Apollo Client is a fully-featured caching GraphQL client with integrations for React, Angular, and more. It allows you to easily build UI components that fetch data via GraphQL. To get the most value out of apollo-client, you should use it with one of its view layer integrations.
To get started with the React integration, go to our React Apollo documentation website.
Apollo Client also has view layer integrations for all the popular frontend frameworks. For the best experience, make sure to use the view integration layer for your frontend framework of choice.
Apollo Client can be used in any JavaScript frontend where you want to use data from a GraphQL server. It's:
Get started on the home page, which has great examples for a variety of frameworks.
# installing the preset package
npm install apollo-boost graphql-tag graphql --save
# installing each piece independently
npm install apollo-client apollo-cache-inmemory apollo-link-http graphql-tag graphql --save
To use this client in a web browser or mobile app, you'll need a build system capable of loading NPM packages on the client. Some common choices include Browserify, Webpack, and Meteor 1.3+.
Install the Apollo Client Developer tools for Chrome for a great GraphQL developer experience!
You get started by constructing an instance of the core class ApolloClient. If you load ApolloClient from the apollo-boost package, it will be configured with a few reasonable defaults such as our standard in-memory cache and a link to a GraphQL API at /graphql.
import ApolloClient from 'apollo-boost';
const client = new ApolloClient();
To point ApolloClient at a different URL, add your GraphQL API's URL to the uri config property:
import ApolloClient from 'apollo-boost';
const client = new ApolloClient({
uri: 'https://graphql.example.com'
});
Most of the time you'll hook up your client to a frontend integration. But if you'd like to directly execute a query with your client, you may now call the client.query method like this:
import gql from 'graphql-tag';
client.query({
query: gql`
query TodoApp {
todos {
id
text
completed
}
}
`,
})
.then(data => console.log(data))
.catch(error => console.error(error));
Now your client will be primed with some data in its cache. You can continue to make queries, or you can get your client instance to perform all sorts of advanced tasks on your GraphQL data. Such as reactively watching queries with watchQuery, changing data on your server with mutate, or reading a fragment from your local cache with readFragment.
To learn more about all of the features available to you through the apollo-client package, be sure to read through the apollo-client API reference.
Read the Apollo Contributor Guidelines.
Running tests locally:
npm install
npm test
This project uses TypeScript for static typing and TSLint for linting. You can get both of these built into your editor with no configuration by opening this project in Visual Studio Code, an open source IDE which is available for free on all platforms.
If you're getting booted up as a contributor, here are some discussions you should take a look at: