apollo-client and react-apollo represent the legacy version 2 stack for Apollo GraphQL, which has since been merged into the unified @apollo/client package. graphql-hooks is a lightweight, hooks-first library focused on simplicity and small bundle size. urql is a modern, highly extensible GraphQL client built by Formidable that balances performance with developer experience. While the Apollo packages were once the industry standard, they are now deprecated, whereas urql and graphql-hooks remain active alternatives for teams seeking different trade-offs in complexity and control.
Building GraphQL applications in React requires a client to handle caching, network requests, and state management. While apollo-client and react-apollo defined the early standard, they are now legacy packages. urql and graphql-hooks offer modern alternatives with different philosophies. Let's compare how they handle setup, data fetching, and caching.
Both apollo-client and react-apollo are deprecated. They represent Apollo version 2. The current standard is @apollo/client, which merges these two packages into one. For the sake of this comparison, we will show the legacy API to illustrate the historical context, but you should not use these packages in new work.
apollo-client handles the core logic like caching and networking. It requires a separate React binding package to work in UI components.
// apollo-client: Core instantiation
import ApolloClient from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
const client = new ApolloClient({
uri: 'https://api.example.com/graphql',
cache: new InMemoryCache()
});
react-apollo provides the React context provider to make the client available across your app. It depends on the core client above.
// react-apollo: React Provider
import { ApolloProvider } from 'react-apollo';
function App() {
return <ApolloProvider client={client}>{/* children */}</ApolloProvider>;
}
graphql-hooks combines client creation and React context in a similar modular way but focuses on hooks from the start.
// graphql-hooks: Client and Provider
import { GraphQLClient, GraphQLHooksProvider } from 'graphql-hooks';
const client = new GraphQLClient({ url: 'https://api.example.com/graphql' });
function App() {
return <GraphQLHooksProvider client={client}>{/* children */}</GraphQLHooksProvider>;
}
urql uses a unified client creation function and a standard Provider component. It is known for its concise setup.
// urql: Client and Provider
import { createClient, Provider } from 'urql';
const client = createClient({ url: 'https://api.example.com/graphql' });
function App() {
return <Provider value={client}>{/* children */}</Provider>;
}
apollo-client does not have React hooks built-in. You must use the core API manually or rely on react-apollo for component integration.
// apollo-client: Core API usage (non-React)
client.query({
query: GET_USER,
variables: { id: 1 }
}).then(({ data }) => {
console.log(data);
});
react-apollo introduces the useQuery hook to handle loading and error states within React components.
// react-apollo: React Hook
import { useQuery } from 'react-apollo';
function UserProfile() {
const { loading, data } = useQuery(GET_USER, { variables: { id: 1 } });
if (loading) return <p>Loading...</p>;
return <div>{data.user.name}</div>;
}
graphql-hooks offers a very similar hook API, keeping the interface familiar for those switching from Apollo.
// graphql-hooks: React Hook
import { useQuery } from 'graphql-hooks';
function UserProfile() {
const { loading, data } = useQuery(GET_USER, { variables: { id: 1 } });
if (loading) return <p>Loading...</p>;
return <div>{data.user.name}</div>;
}
urql also uses a useQuery hook but returns an array-like structure or object depending on version, focusing on simplicity.
// urql: React Hook
import { useQuery } from 'urql';
function UserProfile() {
const [result] = useQuery({ query: GET_USER, variables: { id: 1 } });
const { loading, data } = result;
if (loading) return <p>Loading...</p>;
return <div>{data.user.name}</div>;
}
apollo-client uses a normalized cache by default. It stores objects by ID, allowing updates to reflect across different queries automatically.
// apollo-client: Normalized Cache
// Automatically updates any component showing user:1
// if you write to the cache elsewhere
client.writeQuery({
query: GET_USER,
variables: { id: 1 },
data: { user: { name: 'New Name' } }
});
react-apollo relies entirely on the core client's cache. It does not add caching logic itself but exposes tools to interact with it.
// react-apollo: Cache Interaction
import { useApolloClient } from 'react-apollo';
function UpdateButton() {
const client = useApolloClient();
// Interact with the same cache as apollo-client
client.writeQuery({ ... });
}
graphql-hooks has a simpler cache implementation. It is less opinionated and does not normalize data by default, which reduces complexity but requires manual updates.
// graphql-hooks: Manual Cache
// No automatic normalization
// You must manage cache keys and updates manually
client.setQueryData({ query: GET_USER, variables: { id: 1 } }, newData);
urql uses a configurable cache exchange. It supports normalized caching via an additional package but defaults to a simpler document cache for flexibility.
// urql: Cache Exchange
import { cacheExchange } from '@urql/core';
// Enable normalized caching explicitly
const client = createClient({
url: '...',
exchanges: [cacheExchange, fetchExchange]
});
apollo-client handles mutations through the core client method, returning promises.
// apollo-client: Core Mutation
client.mutate({
mutation: UPDATE_USER,
variables: { id: 1, name: 'New' }
});
react-apollo provides a useMutation hook that returns a trigger function and state.
// react-apollo: Mutation Hook
import { useMutation } from 'react-apollo';
function EditForm() {
const [updateUser] = useMutation(UPDATE_USER);
return <button onClick={() => updateUser({ variables: { id: 1 } })}>Save</button>;
}
graphql-hooks mirrors the Apollo hook pattern closely for mutations.
// graphql-hooks: Mutation Hook
import { useMutation } from 'graphql-hooks';
function EditForm() {
const [updateUser] = useMutation(UPDATE_USER);
return <button onClick={() => updateUser({ variables: { id: 1 } })}>Save</button>;
}
urql uses a useMutation hook that returns an array with the execute function and result state.
// urql: Mutation Hook
import { useMutation } from 'urql';
function EditForm() {
const [result, executeMutation] = useMutation(UPDATE_USER);
return <button onClick={() => executeMutation({ variables: { id: 1 } })}>Save</button>;
}
Despite their differences, all these libraries aim to solve the same problems. Here are key overlaps:
react-apollo) rely on hooks for data fetching.// Common pattern across react-apollo, urql, graphql-hooks
const { loading, data, error } = useQuery(MY_QUERY);
// All support custom headers
// apollo-client: new HttpLink({ headers: { Authorization: '...' } })
// urql: createClient({ fetchOptions: { headers: { ... } } })
// graphql-hooks: new GraphQLClient({ headers: { ... } })
// All packages ship with .d.ts files
// import { useQuery } from 'urql'; // Fully typed
| Feature | apollo-client / react-apollo | graphql-hooks | urql |
|---|---|---|---|
| Status | β οΈ Deprecated (Use @apollo/client) | β Active | β Active |
| Architecture | π Split Core + React Binding | π Split Core + React Binding | π§© Unified + Exchanges |
| Caching | ποΈ Normalized (Automatic) | π Simple (Manual) | βοΈ Configurable (Exchange) |
| Bundle Size | π Large | π¦ Small | π¦ Small |
| Extensibility | π§© Plugins | π Limited | π Exchanges |
apollo-client and react-apollo are legacy tools. They established the patterns we use today but are no longer maintained. If you are on these, plan a migration to @apollo/client.
graphql-hooks is like a lightweight utility belt π β perfect for small projects or teams that want to avoid the complexity of a normalized cache. It gets the job done with minimal setup.
urql is like a modular engineering kit π§° β ideal for teams that want a balance between performance and flexibility. Its exchange system lets you customize behavior without rewriting the core client.
Final Thought: For new projects, avoid the deprecated Apollo v2 packages. Choose urql for extensibility or graphql-hooks for simplicity. If you need the full Apollo ecosystem, use the modern @apollo/client package instead of the legacy ones.
Do not use apollo-client for new projects as it is deprecated and no longer maintained. It was the core logic layer for Apollo v2. You should migrate to @apollo/client which combines this functionality with React bindings. Only consider this if you are maintaining a legacy codebase that has not yet upgraded to version 3.
Choose graphql-hooks if you need a minimal, hooks-only solution without the overhead of a large ecosystem. It is ideal for simple applications where you want direct control over HTTP requests and caching without complex normalized cache configurations. It works well for projects that prioritize bundle size and simplicity over advanced state management features.
Do not use react-apollo for new projects as it is deprecated. It was the React integration layer for Apollo v2. The functionality has been merged into @apollo/client. Choose @apollo/client instead to get the latest hooks, performance improvements, and security patches without changing your workflow significantly.
Choose urql if you want a modern, modular client that balances ease of use with extensibility. It is suitable for teams that need a robust caching strategy and exchange system without the heaviness of Apollo. It shines in projects requiring custom logic for authentication, retries, or offline support through its exchange architecture.
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: