Skip to main content

API Caching

API caching means storing API responses in memory so the app does not need to fetch the same data from the server repeatedly.

It improves performance by reducing latency, avoiding duplicate requests, and decreasing server load.

API Caching = Store fetched API data and reuse it when needed

One-line idea: API Caching = Store API responses in memory so repeated data requests can be served faster with fewer server calls.

Core Concepts​

Why It Matters​

Frontend apps often request the same data multiple times.

Without caching, every component or page may trigger a new network request for the same information.

API caching helps by:

  • reducing repeated API calls
  • making UI updates faster
  • decreasing backend load
  • improving perceived performance
  • sharing fetched data across components
First request -> fetch from server
Next request -> reuse cached response

Common API Caching Libraries​

Several frontend libraries provide API caching features.

LibraryMain Use
React Query / TanStack QueryServer state caching for React apps
SWRLightweight data fetching and caching
Apollo ClientGraphQL query caching with normalized in-memory cache

These libraries handle caching, loading states, error states, background refresh, and cache updates.

Practical Examples​

React Query​

React Query, also known as TanStack Query, provides hooks for fetching and caching API data.

It uses a QueryClient to manage cached server state.

QueryClient = central cache manager for React Query

React Query: React Query Setup​

Create a query client and provide it to the app.

import {
QueryClient,
QueryClientProvider,
useQuery,
} from "@tanstack/react-query";

const queryClient = new QueryClient();

export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
);
}

The QueryClientProvider makes the cache available to components inside the app.

React Query: React Query Example​

Use useQuery() to fetch and cache data.

function Example() {
const { isPending, error, data } = useQuery({
queryKey: ["repoData"],
queryFn: () =>
fetch("https://api.github.com/repos/TanStack/query").then((res) =>
res.json(),
),
});

if (isPending) return "Loading...";

if (error) {
return "An error has occurred: " + error.message;
}

return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
<strong>👀 {data.subscribers_count}</strong>{" "}
<strong>✨ {data.stargazers_count}</strong>{" "}
<strong>🍴 {data.forks_count}</strong>
</div>
);
}

React Query: What Happens in React Query​

The query is identified using a unique key.

queryKey: ["repoData"];

The data is fetched using a query function.

queryFn: () => fetch(url).then((res) => res.json());

React Query then handles:

  • loading state
  • error state
  • response data
  • caching
  • reuse of previously fetched data
Same queryKey -> same cached data

SWR​

SWR uses a global cache by default.

This allows data to be stored and shared across components.

SWR = Data fetching library with shared cache

SWR also allows custom cache behavior using the provider option inside SWRConfig.

SWR: SWR Cache Provider​

A cache provider is a Map-like object.

It supports methods like:

MethodPurpose
getRead cached data
setStore cached data
deleteRemove cached data
keysGet all cache keys

TypeScript-style shape:

interface Cache<Data> {
get(key: string): Data | undefined;
set(key: string, value: Data): void;
delete(key: string): void;
keys(): IterableIterator<string>;
}

SWR: SWR Provider Example​

The provider option receives a function that returns a cache provider.

import useSWR, { SWRConfig } from "swr";

function App() {
return (
<SWRConfig value={{ provider: () => new Map() }}>
<Page />
</SWRConfig>
);
}

All SWR hooks inside this SWRConfig boundary use the provided cache.

SWRConfig boundary -> shared custom cache provider

Apollo Client​

Apollo Client stores GraphQL query results in a local, normalized, in-memory cache.

This allows Apollo to return already cached GraphQL data without sending another network request.

Apollo Cache = Normalized in-memory cache for GraphQL data

Apollo Cache Flow​

The first time an app runs a query for data that is not in cache:

Apollo Client -> InMemoryCache
Cache miss -> GraphQL Server
Server returns data
Data stored in cache
Apollo returns data to component

Later, if the same data is requested again:

Apollo Client -> InMemoryCache
Cache hit -> returns cached data
GraphQL Server is not queried

This makes repeated queries much faster.

Apollo Cache Customization​

Apollo Client cache is highly configurable.

It can be customized for:

  • individual types
  • fields in the schema
  • local data
  • GraphQL query behavior

Apollo can also store and interact with local data that is not fetched from the GraphQL server.

Apollo cache can manage both server data and local data.

Apollo Fetch Policy​

By default, Apollo's useQuery() checks the cache first.

If all requested data is available locally, Apollo returns cached data and does not query the GraphQL server.

This default behavior is called:

cache-first

Apollo Fetch Policy: Fetch Policy Example​

You can change the fetch policy using the fetchPolicy option.

const { loading, error, data } = useQuery(GET_DOGS, {
fetchPolicy: "network-only",
});

Here, network-only means Apollo does not check cache before making the network request.

Network Policies​

Different fetch policies decide how cache and network should be used.

PolicyBehavior
cache-firstUse cache if available, otherwise fetch from network
network-onlyAlways fetch from network
cache-and-networkUse cache first and also fetch latest data from network
cache-lastFetch from network first, use cache only if network fails
no-cacheDo not read from or write to cache

cache-first​

cache-first checks the cache before making a network request.

Cache available -> use cache
Cache missing -> fetch from network

This is Apollo Client's default fetch policy.

It is useful when cached data is acceptable and fast UI response is important.

network-only​

network-only ignores the cache for reading and always sends a network request.

Always fetch from server

Use it when fresh data is more important than cached speed.

In Apollo, network-only may still write the result to cache after fetching.

cache-and-network​

cache-and-network returns cached data first if available, while also making a network request.

When fresh data arrives, the cache updates and the component re-renders.

Show cached data quickly
Fetch latest data in background
Update UI when new data arrives

This is useful when you want both fast UI and fresh data.

cache-last​

cache-last fetches from the network first.

If the network request fails, then the cache is used as a fallback.

Network first
Cache only if network fails

This is useful when fresh data is preferred but cached data is acceptable as a backup.

no-cache​

no-cache bypasses the cache completely.

It does not read from cache and does not write to cache.

No cache read
No cache write
Every request is independent

Use this when every fetch must be separate and cache should not be involved.

API Caching Table​

AreaExplanation
Main goalStore API responses to avoid repeated requests
Storage typeUsually in-memory cache
BenefitLower latency and server load
React QueryUses query keys and query functions
SWRUses global or custom cache providers
Apollo ClientUses normalized GraphQL in-memory cache
Default Apollo policycache-first
Freshness controlManaged through fetch policies and cache behavior

Summary​

API Caching = Store API responses in memory so repeated data requests can be served faster with fewer server calls.

React Query -> queryKey based API cache
SWR -> shared cache provider
Apollo -> normalized GraphQL cache

cache-first -> fast cached response
network-only -> always fresh
cache-and-network -> fast now, fresh later
no-cache -> no cache involvement