GraphQL

Apollo Client Basics

18 min Lesson 19 of 35

Getting Started with Apollo Client

Apollo Client is a comprehensive state management library for JavaScript that enables you to manage both local and remote data with GraphQL. It's the most popular GraphQL client for React, Vue, Angular, and vanilla JavaScript applications.

What is Apollo Client?

Apollo Client provides intelligent caching, declarative data fetching, and powerful developer tools. It handles network requests, caching, error handling, and optimistic UI updates automatically.

Key Features:
  • InMemoryCache: Normalized caching for efficient data storage
  • Declarative data fetching: React hooks like useQuery and useMutation
  • Automatic updates: UI updates when cache changes
  • Error handling: Built-in error states and retry logic
  • DevTools: Browser extension for debugging queries and cache
  • TypeScript support: Full type safety with generated types

Installation and Setup

Install Dependencies:
# Install Apollo Client and GraphQL
npm install @apollo/client graphql

# For React applications
npm install @apollo/client graphql react

Creating Apollo Client Instance

Set up Apollo Client with your GraphQL endpoint and cache configuration.

Basic Apollo Client Setup:
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';

const client = new ApolloClient({
  uri: 'https://api.example.com/graphql',
  cache: new InMemoryCache()
});

export default client;
Advanced Setup with Authentication:
import {
  ApolloClient,
  InMemoryCache,
  HttpLink,
  ApolloLink
} from '@apollo/client';

// HTTP connection to the API
const httpLink = new HttpLink({
  uri: 'https://api.example.com/graphql'
});

// Middleware to add auth token to headers
const authLink = new ApolloLink((operation, forward) => {
  const token = localStorage.getItem('auth_token');

  operation.setContext({
    headers: {
      authorization: token ? `Bearer ${token}` : ''
    }
  });

  return forward(operation);
});

// Create client with auth and cache
const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),
  defaultOptions: {
    watchQuery: {
      fetchPolicy: 'cache-and-network'
    }
  }
});

export default client;

ApolloProvider - Providing Client to React

Wrap your React app with ApolloProvider to make the client available throughout the component tree.

App Setup with ApolloProvider:
import React from 'react';
import { ApolloProvider } from '@apollo/client';
import client from './apollo-client';
import UserList from './components/UserList';

function App() {
  return (
    <ApolloProvider client={client}>
      <div className="App">
        <h1>My GraphQL App</h1>
        <UserList />
      </div>
    </ApolloProvider>
  );
}

export default App;

useQuery Hook - Fetching Data

The useQuery hook executes GraphQL queries and returns loading, error, and data states.

Basic useQuery Example:
import React from 'react';
import { useQuery, gql } from '@apollo/client';

const GET_USERS = gql`
  query GetUsers {
    users {
      id
      name
      email
      avatar
    }
  }
`;

function UserList() {
  const { loading, error, data } = useQuery(GET_USERS);

  if (loading) return <p>Loading users...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h2>Users</h2>
      <ul>
        {data.users.map(user => (
          <li key={user.id}>
            <img src={user.avatar} alt={user.name} />
            <strong>{user.name}</strong> - {user.email}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default UserList;

Query Variables

Pass dynamic variables to your queries for filtering and parameterization.

useQuery with Variables:
import React from 'react';
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({ userId }) {
  const { loading, error, data } = useQuery(GET_USER, {
    variables: { id: userId }
  });

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

  const { user } = data;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <h3>Posts</h3>
      <ul>
        {user.posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

export default UserProfile;

Refetching and Polling

Refetch queries manually or set up automatic polling for real-time updates.

Refetch and Polling Example:
import React from 'react';
import { useQuery, gql } from '@apollo/client';

const GET_STATS = gql`
  query GetStats {
    stats {
      users
      posts
      comments
      lastUpdated
    }
  }
`;

function Dashboard() {
  const { loading, error, data, refetch } = useQuery(GET_STATS, {
    pollInterval: 5000 // Poll every 5 seconds
  });

  if (loading) return <p>Loading stats...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h2>Dashboard</h2>
      <div>Users: {data.stats.users}</div>
      <div>Posts: {data.stats.posts}</div>
      <div>Comments: {data.stats.comments}</div>
      <div>Last Updated: {data.stats.lastUpdated}</div>

      <button onClick={() => refetch()}>
        Refresh Stats
      </button>
    </div>
  );
}

export default Dashboard;

Network Fetch Policies

Apollo Client offers different fetch policies to control how data is fetched and cached.

Fetch Policies:
// cache-first (default): Check cache first, fetch if not found
const { data } = useQuery(QUERY, {
  fetchPolicy: 'cache-first'
});

// cache-and-network: Return cached data, then fetch from network
const { data } = useQuery(QUERY, {
  fetchPolicy: 'cache-and-network'
});

// network-only: Always fetch from network, update cache
const { data } = useQuery(QUERY, {
  fetchPolicy: 'network-only'
});

// no-cache: Always fetch, don't cache result
const { data } = useQuery(QUERY, {
  fetchPolicy: 'no-cache'
});

// cache-only: Only return cached data, never fetch
const { data } = useQuery(QUERY, {
  fetchPolicy: 'cache-only'
});
Fetch Policy Best Practices:
  • Use cache-first for static data that rarely changes
  • Use cache-and-network for data that updates frequently
  • Use network-only for authentication or critical real-time data
  • Use no-cache for sensitive data that shouldn't be cached

Error Handling

Comprehensive Error Handling:
import React from 'react';
import { useQuery, gql } from '@apollo/client';

const GET_POSTS = gql`
  query GetPosts {
    posts {
      id
      title
    }
  }
`;

function Posts() {
  const { loading, error, data } = useQuery(GET_POSTS, {
    onError: (error) => {
      console.error('Query error:', error);
      // Log to error tracking service
    },
    onCompleted: (data) => {
      console.log('Query completed:', data);
    }
  });

  if (loading) return <p>Loading...</p>;

  if (error) {
    // Network error
    if (error.networkError) {
      return <p>Network error. Please check your connection.</p>;
    }

    // GraphQL errors
    if (error.graphQLErrors.length > 0) {
      return (
        <div>
          {error.graphQLErrors.map((err, i) => (
            <p key={i}>Error: {err.message}</p>
          ))}
        </div>
      );
    }

    return <p>Something went wrong.</p>;
  }

  return (
    <ul>
      {data.posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

export default Posts;
Warning: Always handle loading and error states in your components. Unhandled errors can crash your app or leave users staring at blank screens.
Exercise:
  1. Set up Apollo Client in a React app with authentication headers
  2. Create a component that fetches a list of blog posts using useQuery
  3. Add a refetch button that manually refreshes the data
  4. Implement proper loading and error states with user-friendly messages
  5. Experiment with different fetch policies (cache-first, cache-and-network, network-only) and observe the behavior