Apollo Client vs urql in 2026: GraphQL Client Libraries
·PkgPulse Team
TL;DR
Apollo Client for complex GraphQL with caching needs; urql for lighter-weight GraphQL in React. Apollo Client (~6M weekly downloads) is the full-featured option — normalized cache, devtools, subscriptions, local state. urql (~2M downloads) is modular and smaller, with a document cache by default instead of normalized cache. For simple GraphQL data fetching, urql is often sufficient and easier to configure.
Key Takeaways
- Apollo Client: ~6M weekly downloads — urql: ~2M (npm, March 2026)
- Apollo has normalized cache — entities cached by ID, smarter updates
- urql uses document cache by default — simpler, faster, less overhead
- Apollo has better devtools — Apollo DevTools browser extension is excellent
- Both support subscriptions — via WebSocket or SSE
Cache Philosophy
Apollo Client — normalized cache (InMemoryCache):
- Each object is stored by type + ID
- Same object in multiple queries → one cache entry
- Updates in one query automatically reflect in others
- Powerful but complex to configure
- Higher memory usage
urql — document cache (default):
- Caches by query + variables
- Simple: same query → same result
- No normalization → simpler reasoning
- Can opt into normalized cache (urql/graphcache)
- Lower memory usage
Setup
// Apollo Client — setup
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://api.example.com/graphql',
cache: new InMemoryCache({
typePolicies: {
User: {
fields: {
// Merge strategies for pagination, etc.
},
},
},
}),
});
function App() {
return (
<ApolloProvider client={client}>
<MyApp />
</ApolloProvider>
);
}
// urql — minimal setup
import { createClient, Provider } from 'urql';
const client = createClient({
url: 'https://api.example.com/graphql',
fetchOptions: {
headers: { Authorization: `Bearer ${getToken()}` },
},
});
function App() {
return (
<Provider value={client}>
<MyApp />
</Provider>
);
}
Querying
// Apollo Client — useQuery
import { useQuery, gql } from '@apollo/client';
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
posts { id title }
}
}
`;
function UserProfile({ id }) {
const { data, loading, error } = useQuery(GET_USER, {
variables: { id },
fetchPolicy: 'cache-first', // 'cache-and-network', 'network-only', etc.
});
if (loading) return <Spinner />;
if (error) return <Error />;
return <div>{data.user.name}</div>;
}
// urql — useQuery
import { useQuery, gql } from 'urql';
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id name email
posts { id title }
}
}
`;
function UserProfile({ id }) {
const [result] = useQuery({ query: GET_USER, variables: { id } });
const { data, fetching, error } = result;
if (fetching) return <Spinner />;
if (error) return <Error />;
return <div>{data.user.name}</div>;
}
Mutations
// Apollo Client — useMutation with cache update
import { useMutation, gql } from '@apollo/client';
const CREATE_POST = gql`
mutation CreatePost($title: String!, $content: String!) {
createPost(title: $title, content: $content) { id title }
}
`;
function NewPostForm() {
const [createPost, { loading, error }] = useMutation(CREATE_POST, {
update(cache, { data: { createPost } }) {
cache.modify({
fields: {
posts(existingPosts = []) {
const newPostRef = cache.writeFragment({
data: createPost,
fragment: gql`fragment NewPost on Post { id title }`,
});
return [...existingPosts, newPostRef];
},
},
});
},
});
const handleSubmit = () =>
createPost({ variables: { title, content } });
}
// urql — useMutation
import { useMutation, gql } from 'urql';
const CREATE_POST = gql`
mutation CreatePost($title: String!, $content: String!) {
createPost(title: $title, content: $content) { id title }
}
`;
function NewPostForm() {
const [result, createPost] = useMutation(CREATE_POST);
const { fetching, error } = result;
const handleSubmit = () =>
createPost({ title, content });
// urql's document cache auto-invalidates related queries
// No manual cache update needed for simple cases
}
When to Choose
Choose Apollo Client when:
- Large GraphQL schemas with complex entity relationships
- Normalized cache is needed (same entity in many queries)
- Apollo DevTools are valuable for your team
- Optimistic UI updates and rollback
- Local state management via Apollo reactive variables
Choose urql when:
- Simpler GraphQL usage (mostly queries)
- Bundle size matters (~15KB vs Apollo's ~40KB gzipped)
- You want easier configuration without typePolicies
- Document-cache behavior is sufficient (most CRUD apps)
- Team is building with urql's exchange system for customization
Compare Apollo Client and urql package health on PkgPulse.
See the live comparison
View apollo client vs. urql on PkgPulse →