Imagine an assistant with a notebook. You ask them to find out the list of posts: they go to the server, bring back the answer and write it into the notebook — not randomly, but on "cards". Every post has its own card Post:1, every author has User:7. Next time the assistant answers from the notebook, and when a card changes, they tell every screen that shows it. That is Apollo Client: it handles GraphQL requests, loading and error states, the cache and the updating of the interface. In this lesson you connect it to the React client of the blog, show the list of posts, add a form, learn to update the cache after mutations and send the JWT.

What a GraphQL client gives you

You can get by with plain fetch, as in the first lesson, but you would have to track loading and error by hand, avoid duplicate requests and make sure that after a mutation every screen shows the new data. A client does it for you. Compare it with what you already know:

  • Apollo Client understands GraphQL and stores data normalized: by objects, not by requests. The most popular choice.
  • TanStack Query (the React topic) caches responses by query keys and suits any API, but it does not "know" GraphQL objects.
  • urql is a light client: by default it caches whole documents, and a normalized cache is added by a separate package.

Connecting Apollo Client

Create a Vite project and install the packages. Apollo Client 4 also needs rxjs. The server address lives in .env: Vite exposes to the browser only variables with the VITE_ prefix.

bash
npm create vite@latest blog-web -- --template react-ts
cd blog-web
npm install @apollo/client graphql rxjs
# then create .env with the line: VITE_GRAPHQL_URL=http://localhost:4000/graphql

In version 4 the link to the server is created explicitly (new HttpLink), and the hooks and ApolloProvider come from @apollo/client/react. Create the client once and wrap the app in it:

tsx
// src/apollo.ts
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';

export const client = new ApolloClient({
  link: new HttpLink({ uri: import.meta.env.VITE_GRAPHQL_URL }),
  cache: new InMemoryCache(),
});

// src/main.tsx
import { ApolloProvider } from '@apollo/client/react';
import { client } from './apollo.ts';

createRoot(document.getElementById('root')!).render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
);

useQuery: the list of posts

A query is written in a gql template string. The useQuery hook sends it when the component is first shown and returns the state:

tsx
// src/PostList.tsx
import { gql } from '@apollo/client';
import { useQuery } from '@apollo/client/react';

type Post = { id: string; title: string; author: { name: string } };

const POSTS = gql`
  query Posts($published: Boolean) {
    posts(published: $published) {
      id
      title
      author { id name }
    }
  }
`;

export function PostList() {
  const { data, loading, error, refetch } = useQuery<{ posts: Post[] }>(
    POSTS,
    { variables: { published: true } },
  );

  if (loading && !data) return <p>Loading…</p>;
  if (error) return <p>Error: {error.message}</p>;