Labs ICT
Pro Login

GraphQL Caching

GraphQL Caching

Caching is one of GraphQL's superpowers. Apollo Client's cache stores query results so you don't have to refetch data you've already loaded.

Understanding how caching works helps you build faster, more efficient applications.

How Apollo Cache Works

Apollo Client uses a normalized cache. This means it stores each object separately by its type and id, then references them.

// When you query this:
query {
  user(id: "1") {
    name
    email
  }
}

// Apollo stores it like this:
// User:1: { name: "Alice", email: "alice@example.com" }

// If you query the same user again:
query {
  user(id: "1") {
    name
    email
    age  // New field
  }
}

// Apollo merges the data:
// User:1: { name: "Alice", email: "alice@example.com", age: 25 }

The cache is normalized, meaning objects are stored by their id and shared across queries.

Fetch Policies

Fetch policies control when Apollo uses the cache vs the network:

import { useQuery } from '@apollo/client';

// Default: Use cache if available, otherwise network
useQuery(GET_USER, { fetchPolicy: 'cache-first' });

// Always go to network first
useQuery(GET_USER, { fetchPolicy: 'network-only' });

// Use cache, but also update it from network
useQuery(GET_USER, { fetchPolicy: 'cache-and-network' });

// Only use cache, never network
useQuery(GET_USER, { fetchPolicy: 'cache-only' });

// Don't use cache, always network (but update cache)
useQuery(GET_USER, { fetchPolicy: 'no-cache' });

Choose the right policy based on your data freshness requirements.

Cache Invalidation

Sometimes you need to invalidate cache entries. Apollo provides several ways to do this:

// Invalidate a specific query
const { data } = useQuery(GET_USER, {
  variables: { id: userId },
});

// Force refetch
const { refetch } = useQuery(GET_USER);
await refetch();

// Clear entire cache
client.cache.reset();

// Remove specific items from cache
cache.evict({ id: 'User:1' });
cache.gc(); // Clean up unreachable objects

Use cache invalidation sparingly. It's usually better to update the cache surgically.

Cache Keys

Apollo uses __typename and id to create cache keys. If your objects don't have ids, you need to tell Apollo how to identify them:

const cache = new InMemoryCache({
  typePolicies: {
    User: {
      keyFields: ['id'], // Use id as cache key
    },
    Product: {
      keyFields: ['sku'], // Use sku instead of id
    },
    CartItem: {
      keyFields: ['productId', 'size'], // Composite key
    },
  },
});

Proper cache key configuration is essential for the cache to work correctly.

Best Practices

Use consistent ids: Every object should have a unique id field.

Update cache after mutations: Use update functions or refetchQueries to keep the cache fresh.

Monitor cache size: Large caches can consume memory. Use cache.gc() to clean up.

Test cache behavior: Use Apollo DevTools to inspect the cache and verify it's working correctly.