Unleash the Power of GraphQL APIs: Create Stunning Frontends with Apollo Client

As a frontend engineer, few things excite me more than creating fast, interactive UIs that talk to powerful backends. One of the most transformative experiences I’ve had in recent years was integrating GraphQL into my frontend workflow, and especially mastering the Apollo Client to consume GraphQL APIs.

In this post, I’m going to take you through a real-world, senior-level walkthrough of how to build a production-ready, blazing-fast React frontend powered by Apollo Client and GraphQL APIs. This isn’t a surface-level tutorial — it’s packed with the exact problems I’ve faced, decisions I’ve made, and lessons I’ve learned along the way.

If you’re tired of REST limitations, over-fetching, or just want to build UIs like a frontend god — read on.


🔍 Why GraphQL Over REST?

Before we even touch a line of code, let’s address the big “why.”

REST is simple — but limited:

  • You often over-fetch or under-fetch data.

  • You deal with multiple roundtrips.

  • Versioning APIs becomes messy.

GraphQL flips that on its head:

  • Ask for exactly what you need.

  • Combine multiple resources in a single query.

  • Strongly typed schema — frontend and backend are in sync.

For teams building fast, evolving UIs, GraphQL is a game-changer.


🛠️ Project Setup: What We’re Building

We’re building a React frontend that consumes a GraphQL API for a blog app. We’ll:

  • Fetch and display a list of blog posts.

  • View a single post’s details.

  • Use GraphQL queries and mutations.

  • Handle loading, error, and success states elegantly.

Tools:

  • React (with Vite or CRA)

  • Apollo Client

  • GraphQL

  • TypeScript (optional but recommended)


1️⃣ Installing Apollo Client and GraphQL

Start with a fresh React app:

npx create-react-app graphql-blog
cd graphql-blog
npm install @apollo/client graphql

Now set up the ApolloProvider at the root.

// src/main.jsx or src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import {
  ApolloClient,
  InMemoryCache,
  ApolloProvider,
} from '@apollo/client';

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

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

✅ That’s all you need to get started. Next, let’s fire a query.

2️⃣ Writing Your First GraphQL Query with Apollo

Here’s a simple GraphQL query to get launches from SpaceX API:

query GetLaunches {
  launchesPast(limit: 5) {
    mission_name
    launch_date_local
    rocket {
      rocket_name
    }
  }
}

We’ll run this in React with useQuery():

// src/Launches.jsx
import { gql, useQuery } from '@apollo/client';

const GET_LAUNCHES = gql`
  query GetLaunches {
    launchesPast(limit: 5) {
      mission_name
      launch_date_local
      rocket {
        rocket_name
      }
    }
  }
`;

export default function Launches() {
  const { loading, error, data } = useQuery(GET_LAUNCHES);

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

  return (
    <div>
      {data.launchesPast.map((launch, i) => (
        <div key={i}>
          <h3>{launch.mission_name}</h3>
          <p>{launch.rocket.rocket_name}</p>
          <p>{new Date(launch.launch_date_local).toLocaleDateString()}</p>
        </div>
      ))}
    </div>
  );
}

Add this component to App.jsx:

import Launches from './Launches';

function App() {
  return (
    <div className="App">
      <h1>SpaceX Launches</h1>
      <Launches />
    </div>
  );
}

3️⃣ Handling Errors Like a Senior Dev

One of the rookie mistakes I’ve seen (and made) is just logging the error. Here’s a cleaner way:

if (error) {
  console.error(error);
  return <p>Something went wrong. Please try again later.</p>;
}

Even better — create a reusable ErrorBoundary component for all GraphQL errors and wrap key parts of your app.


4️⃣ Pagination with Apollo

Pagination is easier with GraphQL than REST.

Update your query:

query GetLaunches($limit: Int!, $offset: Int!) {
  launchesPast(limit: $limit, offset: $offset) {
    mission_name
  }
}

Use fetchMore():

const { data, fetchMore } = useQuery(GET_LAUNCHES, {
  variables: { limit: 5, offset: 0 },
});

const loadMore = () => {
  fetchMore({
    variables: { offset: data.launchesPast.length },
  });
};

Now add a button:

<button onClick={loadMore}>Load More</button>

5️⃣ Adding Mutations

const ADD_POST = gql`
  mutation AddPost($title: String!, $content: String!) {
    addPost(title: $title, content: $content) {
      id
      title
    }
  }
`;

const [addPost] = useMutation(ADD_POST);

const handleSubmit = () => {
  addPost({ variables: { title: 'Hello', content: 'World' } });
};

6️⃣ Real-Time Updates with Subscriptions

GraphQL Subscriptions allow real-time updates. If your backend supports it (e.g., Apollo Server with WebSocket), you can use:

import { gql, useSubscription } from '@apollo/client';

const NEW_MESSAGES = gql`
  subscription OnMessageAdded {
    messageAdded {
      id
      content
    }
  }
`;

function MessageList() {
  const { data, loading } = useSubscription(NEW_MESSAGES);
  if (loading) return <p>Loading...</p>;
  return <div>{data.messageAdded.content}</div>;
}

To enable this, configure split links:

const wsLink = new GraphQLWsLink(createClient({ url: 'ws://localhost:4000/graphql' }));
const httpLink = createHttpLink({ uri: 'http://localhost:4000/graphql' });

const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  wsLink,
  httpLink
);

const client = new ApolloClient({
  link: splitLink,
  cache: new InMemoryCache(),
});

7️⃣ Deep Dive: Apollo Caching Strategies

Apollo’s caching engine is powerful. Here’s what you should understand:

Cache Policies

  • cache-first (default): Pulls from cache, then fetches.

  • network-only: Ignores cache.

  • cache-and-network: Shows cache while fetching new.

Identifying Data

To normalize properly:

const cache = new InMemoryCache({
  typePolicies: {
    Post: {
      keyFields: ['id'],
    },
  },
});

Updating the Cache After Mutation

8️⃣ Generating Types with GraphQL Codegen

To avoid manual types, use Codegen:

npm install @graphql-codegen/cli
npx graphql-codegen init

This will:

  • Scan your GraphQL queries

  • Generate TS typings

  • Strongly type useQuery, useMutation, etc.

Example usage:

const { data } = useGetLaunchesQuery();

Less bugs. More DX. Highly recommended.

🧠 Pro Tips from My Real Projects

  • Use fragments to reuse query structures.

  • Never hardcode strings — use .graphql files with tooling.

  • Bundle Apollo logic in services/ folder.

  • Memoize derived data with useMemo().

  • Split components logically: loading UI, error UI, and main UI.

🚀 Conclusion

GraphQL + Apollo Client = A modern frontend’s best friend.

By the time I integrated Apollo into my workflow, my UIs felt smarter, leaner, and more resilient. I could query exactly what I wanted, handle changes elegantly, and write frontend code that felt... precise.

This wasn’t just a productivity win — it elevated my status as a frontend engineer.

If you haven’t tried Apollo Client seriously yet, this is your sign.


📌 Save this post — You’ll thank yourself when you're staring at a nested REST call wondering if there’s a better way (there is).

👋 Got questions or want to collaborate? Find me on GitHub or drop a comment below.

Next Up? I’ll be covering GraphQL Code Generator, TypeScript integration with Apollo, and GraphQL caching patterns.

Till then — happy querying, devs. 🧠⚡

0
Subscribe to my newsletter

Read articles from Abdulmalik Adekunle directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Abdulmalik Adekunle
Abdulmalik Adekunle

Frontend software engineer and technical writer passionate about creating dynamic, interactive and engaging web applications that bring value to businesses and their customers. With expertise in React, Next.js, Javascript, Typescript, Nodejs, CSS, HTML e.t.c.