Skip to main content

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.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.