Introduction to GraphQL
GraphQL is an API query language that fetches data based only on what the client application needs. It solves the problem of clients fetching unnecessary data thus making APIs more scalable. The precision in data fetching is the power of GraphQL. Find out more on the official GraphQL website.
In this tutorial, we will learn how to work with Apollo Client, a JavaScript state management library that assists you manage GraphQL both remotely and locally. We shall learn to use it on the client side and server side in Next JS apps.
Setting Up the Project
Prerequisites
- Node.js 18.18 or later.
- Knowledge of GraphQL and how it works. Find out more on the official GraphQL website.
- Next JS 14 or later.
Type this command in your terminal to create a new project.
npx create-next-app@latest
When installing, you will see the following prompts. Ensure you choose the App Router.
What is your project named? graphql-app
Would you like to use TypeScript? No / Yes
Would you like to use ESLint? No / Yes
Would you like to use Tailwind CSS? No / Yes
Would you like your code inside a `src/` directory? No / Yes
Would you like to use App Router? (recommended) No / Yes
Would you like to use Turbopack for `next dev`? No / Yes
Would you like to customize the import alias (`@/*` by default)? No / Yes
What import alias would you like configured? @/*
Installing Dependencies
As mentioned, we shall learn data-fetching in server and client components. Apollo Client provides an experimental library to facilitate this. Install the following dependencies.
npm install @apollo/client @apollo/experimental-nextjs-app-support
Server Components
We shall begin with data fetching from a GraphQL API in server components.
Creating the Apollo Client Instance
Firstly, we will create an Apollo Client instance that we shall use throughout our app.
// ./ApolloClient
import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";
import { registerApolloClient } from "@apollo/experimental-nextjs-app-support";
export const { getClient } = registerApolloClient(() => {
return new ApolloClient({
cache: new InMemoryCache({ addTypename: false }),
link: new HttpLink({
uri: "https://rickandmortyapi.com/graphql",
}),
});
});
Let’s break it down step by step:
- registerApolloClient: This function takes a callback which initializes and returns an instance of Apollo Client. This is specifically for Next JS since it allows use with the framework’s server-side rendering (SSR).
- Apollo Client Configuration: The configuration for Apollo Client is done inside the callback function passed to registerApolloClient.
cache: new InMemoryCache({ addTypename: false }),
- Initializes an InMemoryCache for data storage and management of GraphQL queries.
- addTypename: false: ensures the response from the query does not have the _typename in the result. It makes it easier to display the result in the front end.
link: new HttpLink({
uri: “https://rickandmortyapi.com/graphql",
}),
- Establishes an HTTP link that Apollo Client uses to send queries to the endpoint we specify.
Create this file in the root of your Next JS app folder so that the instance is available to all pages in the app router.
Creating the query
Next, we shall create the query that fetches the data we wish to display in our Next JS app. Create a folder at the project’s root called queries where the queries will be stored.
Create a file called characterQuery and paste the following code.
import { gql } from "@apollo/client";
export const characterQuery = gql`
query {
characters {
results {
name
status
species
image
origin {
name
}
location {
name
}
}
}
}
`;
The code defines the character query which requests the characters from the endpoint with specific fields with data about the characters.
The data from the query is rendered in the component below. Let us discuss what the code does.
import { getClient } from "./ApolloClient";
import Image from 'next/image'
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { characterQuery } from "@/queries/characterQuery";
import { Character, CharacterQueryResponse } from "@/types/types";
export default async function Home() {
const { data } = await getClient().query<CharacterQueryResponse>({ query: characterQuery });
return (
<div className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{data.characters.results.map((character:Character, index:number) => (
<Card key={index} className="max-w-sm mx-auto my-4">
<CardHeader className="relative">
<Image src={character.image} width={300} height={300} alt={character.name} />
<Button className="absolute right-2 top-2">{character.status}</Button>
<CardTitle>{character.name}</CardTitle>
</CardHeader>
<CardContent>
<p>{character.species}</p>
<p>Origin: {character.origin.name}</p>
<p>Location: {character.location.name}</p>
</CardContent>
</Card>
))}
</div>
</div>
);
}
We begin by fetching the data.
const { data } = await getClient().query<CharacterQueryResponse>({ query: characterQuery });
- getClient().query({ query: characterQuery }) calls the Apollo Client instance’s query method passing in characterQuery to fetch character data from the API.
- The data is rendered in the form of cards. Feel free to play around with the styling.
Client Components
This data fetching method applies to client components, in the event you wish to use state or context depending on your use case.
We begin by creating a provider for Apollo that passes the client to the hooks.
// ./apollo-wrapper.tsx
"use client";
import { ApolloLink, HttpLink } from "@apollo/client";
import {
ApolloClient,
ApolloNextAppProvider,
InMemoryCache,
SSRMultipartLink,
} from "@apollo/experimental-nextjs-app-support";
function makeClient() {
const httpLink = new HttpLink({
uri: "https://rickandmortyapi.com/graphql",
});
return new ApolloClient({
cache: new InMemoryCache(),
link:
typeof window === "undefined"
? ApolloLink.from([
// in a SSR environment, if you use multipart features like
// @defer, you need to decide how to handle these.
// This strips all interfaces with a `@defer` directive from your queries.
new SSRMultipartLink({
stripDefer: true,
}),
httpLink,
])
: httpLink,
});
}
export function ApolloWrapper({ children }: React.PropsWithChildren) {
return (
<ApolloNextAppProvider makeClient={makeClient}>
{children}
</ApolloNextAppProvider>
);
}
Let’s break it down step by step:
We create an HTTP link that Apollo Client uses to send queries to the endpoint we specify
Then we define a function called makeClient which creates an Apollo Client instance for SSR and client environments.
function makeClient() {
const httpLink = new HttpLink({
uri: "https://rickandmortyapi.com/graphql",
});
return new ApolloClient({
cache: new InMemoryCache(),
link:
typeof window === "undefined"
? ApolloLink.from([
// in a SSR environment, if you use multipart features like
// @defer, you need to decide how to handle these.
// This strips all interfaces with a `@defer` directive from your queries.
new SSRMultipartLink({
stripDefer: true,
}),
httpLink,
])
: httpLink,
});
}
- SSRMultipartLink: A link specifically designed for handling multipart responses in SSR.
- stripDefer: true: Strips interfaces with @defer directives from queries.
- The @defer Directive, when enabled, allows portions of a query to load separately for faster page rendering. Stripping it for SSR simplifies processing, especially since SSR environments may not handle @defer the same way as the client.
- The link configuration has a condition for SSR and client environments where the link is used directly in client environments.
export function ApolloWrapper({ children }: React.PropsWithChildren) {
return (
<ApolloNextAppProvider makeClient={makeClient}>
{children}
</ApolloNextAppProvider>
);
}
In the layout file, we shall add the Apollo Wrapper.
// ./layout
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { ApolloWrapper } from "./apollo-wrapper";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Rick & Morty App",
description: "GraphQL Demo app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<ApolloWrapper>
<body className={inter.className}>{children}</body>
</ApolloWrapper>
</html>
);
}
We then use the Apollo Wrapper we created above to wrap the body of the content which ensures that all pages have Apollo Client access.
// ./page.tsx
'use client'
import { Character, CharacterQueryResponse } from "@/types/types";
import Image from 'next/image'
import { useSuspenseQuery } from '@apollo/client';
import { characterQuery } from '@/queries/characterQuery';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
export default function Home() {
const { data } = useSuspenseQuery<CharacterQueryResponse>(characterQuery);
return(
<div className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{data.characters.results.map((character:Character, index:number) => (
<Card key={index} className="max-w-sm mx-auto my-4">
<CardHeader className="relative">
<Image src={character.image} width={300} height={300} alt={character.name} />
<Button className="absolute right-2 top-2">{character.status}</Button>
<CardTitle>{character.name}</CardTitle>
</CardHeader>
<CardContent>
<p>{character.species}</p>
<p>Origin: {character.origin.name}</p>
<p>Location: {character.location.name}</p>
</CardContent>
</Card>
))}
</div>
</div>
</div>
)
}
- useSuspenseQuery allows for streaming, as it does in SSR rendering. Here we pass in characterQuery to fetch character data from the API.
const { data } = useSuspenseQuery<CharacterQueryResponse>(characterQuery);
Conclusion
This article discusses integrating GraphQL in Next JS using Apollo Client and GraphQL concepts related to front-end development. The project is available here.
If you wish to discuss the topic further, contact me on Linkedin.
Thank you for reading! Until next time, may the code be with you.
Top comments (0)