Apollo Client Reference: useQuery, useMutation, Cache, Subscriptions & Codegen
Apollo Client is the most widely-used GraphQL client for JavaScript. It handles data fetching, caching, and state management for GraphQL queries. The killer feature is the normalized in-memory cache: results are stored by type + ID, so updating one object updates it everywhere it’s referenced in your UI without any extra work. For simple read-heavy apps you can replace most of your state management with Apollo queries.
1. Setup & useQuery
ApolloClient setup, InMemoryCache, ApolloProvider, and the useQuery hook
// npm install @apollo/client graphql
// client.ts — configure the Apollo client:
import { ApolloClient, InMemoryCache, createHttpLink } from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
const httpLink = createHttpLink({ uri: "https://api.example.com/graphql" });
// Add auth header to every request:
const authLink = setContext((_, { headers }) => ({
headers: { ...headers, authorization: `Bearer ${localStorage.getItem("token")}` }
}));
export const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});
// Wrap your app:
import { ApolloProvider } from "@apollo/client";
root.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
// useQuery — fetch data declaratively:
import { useQuery, gql } from "@apollo/client";
const GET_USERS = gql`
query GetUsers($limit: Int) {
users(limit: $limit) {
id
name
email
role
}
}
`;
function UserList() {
const { loading, error, data } = useQuery(GET_USERS, {
variables: { limit: 20 },
// fetchPolicy: "cache-first" (default — check cache first)
// fetchPolicy: "network-only" (skip cache, always fetch)
// fetchPolicy: "cache-and-network" (show cached instantly, update in background)
pollInterval: 30000, // refetch every 30s
});
if (loading) return <Spinner />;
if (error) return <p>Error: {error.message}</p>;
return <ul>{data.users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
2. useMutation & Cache Updates
useMutation, optimistic updates, and manually updating the cache after mutation
import { useMutation, gql } from "@apollo/client";
const CREATE_USER = gql`
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
`;
function CreateUserForm() {
const [createUser, { loading, error }] = useMutation(CREATE_USER, {
// Option 1: refetch queries after mutation:
refetchQueries: [{ query: GET_USERS, variables: { limit: 20 } }],
// Option 2: manually update cache (faster — no extra network request):
update(cache, { data: { createUser } }) {
cache.modify({
fields: {
users(existingUsers = []) {
const newRef = cache.writeFragment({
data: createUser,
fragment: gql`fragment NewUser on User { id name email }`,
});
return [...existingUsers, newRef];
},
},
});
},
// Option 3: optimistic response (show result before server confirms):
optimisticResponse: {
createUser: { __typename: "User", id: "temp-id", name: "Alice", email: "alice@example.com" },
},
});
const handleSubmit = async (values) => {
try {
const { data } = await createUser({ variables: { input: values } });
console.log("Created:", data.createUser);
} catch (e) {
console.error("Mutation failed:", e.message);
}
};
return <form onSubmit={handleSubmit}>...</form>;
}
// Delete and evict from cache:
const DELETE_USER = gql`mutation DeleteUser($id: ID!) { deleteUser(id: $id) { id } }`;
const [deleteUser] = useMutation(DELETE_USER, {
update(cache, { data: { deleteUser } }) {
cache.evict({ id: cache.identify(deleteUser) }); // remove by type+id
cache.gc(); // garbage collect unreachable objects
},
});
3. Cache Normalization & Policies
How InMemoryCache normalizes data, keyFields, typePolicies, and field merge functions
// InMemoryCache normalizes by __typename + id by default
// Every User with id "123" is stored once at User:123
// All queries that returned that user reference the SAME cache entry
// Update it once → all components re-render with new data
// typePolicies — customize cache key or field behavior:
const cache = new InMemoryCache({
typePolicies: {
User: {
keyFields: ["email"], // use email as unique key instead of id
},
Query: {
fields: {
// Paginated field — merge pages together instead of replacing:
users: {
keyArgs: ["filter"], // only these args matter for cache key
merge(existing = [], incoming) {
return [...existing, ...incoming]; // append new page
},
read(existing) {
return existing; // always read from merged array
},
},
},
},
},
});
// Read directly from cache (no network):
const user = client.readFragment({
id: "User:123",
fragment: gql`fragment UserFields on User { id name email }`,
});
// Write directly to cache (optimistic UI, offline, tests):
client.writeFragment({
id: "User:123",
fragment: gql`fragment UserFields on User { id name email }`,
data: { id: "123", name: "Alice Updated", email: "alice@example.com" },
});
// Invalidate + refetch specific queries:
client.refetchQueries({ include: [GET_USERS] });
// Clear entire cache:
client.clearStore(); // clears and resets active queries
client.resetStore(); // clears and refetches active queries
4. useSubscription, Lazy Queries & Error Handling
WebSocket subscriptions, useLazyQuery for on-demand fetching, and error policies
// WebSocket for subscriptions:
// npm install @apollo/client subscriptions-transport-ws graphql-ws
import { split } from "@apollo/client";
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
import { getMainDefinition } from "@apollo/client/utilities";
const wsLink = new GraphQLWsLink(createClient({ url: "ws://localhost:4000/graphql" }));
// Route subscriptions over WebSocket, queries/mutations over HTTP:
const splitLink = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query) as any;
return kind === "OperationDefinition" && operation === "subscription";
},
wsLink,
authLink.concat(httpLink),
);
const client = new ApolloClient({ link: splitLink, cache: new InMemoryCache() });
// useSubscription:
const MESSAGES_SUB = gql`subscription { messageSent { id text author { name } } }`;
const { data: sub } = useSubscription(MESSAGES_SUB, {
onData: ({ data }) => console.log("New message:", data.data.messageSent),
});
// useLazyQuery — execute on demand (e.g., on button click, not on mount):
const [searchUsers, { loading, data }] = useLazyQuery(SEARCH_USERS);
<button onClick={() => searchUsers({ variables: { query: "alice" } })}>Search</button>
// Error handling — separate network errors from GraphQL errors:
const { error } = useQuery(GET_USERS);
if (error?.networkError) return <p>Network error: {error.networkError.message}</p>;
if (error?.graphQLErrors.length) return <p>{error.graphQLErrors[0].message}</p>;
5. TypeScript Code Generation & Apollo Server
graphql-codegen for typed operations, and Apollo Server 4 basics
// graphql-codegen — generate TypeScript types from schema:
// npm install -D @graphql-codegen/cli @graphql-codegen/typescript
// @graphql-codegen/typescript-operations
// @graphql-codegen/typescript-react-apollo
// codegen.ts:
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
schema: "http://localhost:4000/graphql", // or local .graphql files
documents: ["src/**/*.tsx", "src/**/*.ts"],
generates: {
"./src/generated/graphql.ts": {
plugins: ["typescript", "typescript-operations", "typescript-react-apollo"],
config: { withHooks: true },
},
},
};
export default config;
// After running npx graphql-codegen, use typed hooks:
// BEFORE (untyped):
const { data } = useQuery(GET_USERS);
data.users[0].name; // TypeScript doesn't know the shape
// AFTER (fully typed):
import { useGetUsersQuery } from "./generated/graphql";
const { data } = useGetUsersQuery({ variables: { limit: 20 } });
data?.users[0].name; // TypeScript knows every field
// Apollo Server 4 basics (backend):
// npm install @apollo/server graphql
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
const server = new ApolloServer({
typeDefs: `
type User { id: ID!; name: String!; email: String! }
type Query { users: [User!]! }
`,
resolvers: {
Query: { users: () => db.users.findAll() },
},
});
const { url } = await startStandaloneServer(server, { listen: { port: 4000 } });
// Or with Express: expressMiddleware(server) for existing Express app
Track Node.js and GraphQL releases at ReleaseRun. Related: GraphQL Reference | tRPC Reference | TanStack Query Reference
🔍 Free tool: npm Package Health Checker — check Apollo Client and related GraphQL packages for known CVEs and active maintenance.
Founded
2023 in London, UK
Contact
hello@releaserun.com