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. 🧠⚡
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.