Chapter 5: Apollo Client
Strongly Typed Next.js
link Apollo Client
By the end of this lesson, developers will be able to:
- Create an Apollo Client instance on the frontend web
- Generate queries and mutations with GraphQL codegen
Introduction
In the previous section, we completed the implementation for our GraphQL API server. In this section, we switch back to the frontend client and start generating queries and mutations.
GraphQL Codegen
In this course, we are able to leverage Typescript in a variety of ways. The most efficient use of Typescript and GraphQL is generating frontend queries, using the GraphQL Codegen library.
Using GraphQL Codegen, we generate queries and mutations for the frontend client.
link Installation
Heads Up! This lesson's code edits will only affect your APP project.
First, change directory into your root level app
folder:
cd app
Let's begin with the installation of GraphQL Codegen and GraphQL Let:
npm install --save-dev graphql-let @graphql-codegen/cli @graphql-codegen/plugin-helpers @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo yaml-loader
npm install @apollo/client graphql
npx graphql-let init
GraphQL Let
Similar to GraphQL Codegen, GraphQL Let allows us to reference types that belong to our API, and generate queries for the frontend. The major difference with the GraphQL Let library is that it generates typed files (example.d.ts
) for each query.
For an overview of GraphQL Let, click here.
Getting started with GraphqL Codegen, we will create a file to configure it's behavior.
Create a new file, app/.graphql-let.yml
and insert the following:
schema: 'lib/schema.graphqls'
documents: '**/*.graphql'
plugins:
- typescript
- typescript-operations
- typescript-react-apollo
cacheDir: __generated__
In this file, we specify where the schema definition lives. In this case, we are pulling the schema definition from our local GraphQL API server.
Copy Schema
Grab a copy of the GraphQL schema by copying the entire file api/schema/schema.gql
and pasting it's contents inside a new file called app/lib/schema.graphqls
.
You may noticed we referenced this new file lib/schema.graphqls
in the previous step. Be sure to give it the correct file extension: .graphqls
, otherwise GraphQL Codegen will not work.
Git Ignore
Before moving forward, add the following to your root level .gitignore
file:
*.graphql.d.ts
*.graphqls.d.ts
__generated__
Next.js Config
Add the following to app/next-env.d.ts
:
/// <reference types="next" />
/// <reference types="next/types/global" />
declare module '*.graphqls' {
import { DocumentNode } from 'graphql';
export default typeof DocumentNode;
}
declare module '*.yml';
We made this change to allow Next.js to read .yml
files.
Webpack Config
Let's add webpack support for GraphQL Codegen with the following changes in next.config.js
:
module.exports = {
webpack(config, options) {
config.module.rules.push({
test: /\.graphql$/,
exclude: /node_modules/,
use: [options.defaultLoaders.babel, { loader: 'graphql-let/loader' }],
});
config.module.rules.push({
test: /\.graphqls$/,
exclude: /node_modules/,
use: ['graphql-let/schema/loader'],
});
config.module.rules.push({
test: /\.ya?ml$/,
type: 'json',
use: 'yaml-loader',
});
return config;
},
};
link Generate Queries
With GraphQL Codegen configured, we will create three new queries for the client:
currentUser
stream
streams
First, create a new directory and file, called app/lib/graphql
and app/lib/graphql/currentUser.graphql
:
query CurrentUser {
currentUser {
_id
email
}
}
Every query needs a name, so we called this one CurrentUser
.
Second, create a new file, called app/lib/graphql/stream.graphql
:
query Stream($streamId: ObjectId!) {
stream(streamId: $streamId) {
_id
title
description
url
author {
_id
email
}
}
}
Note that this query requires an input called streamId
. We will use it to fetch individual streams.
Third, create a new file, called app/lib/graphql/streams.graphql
:
query Streams {
streams {
_id
title
description
url
}
}
Note that this query fetches the current user's streams, so they will need to be logged in.
link Generate Mutations
We are going to repeat the above process for five new mutations:
createStream
editStream
deleteStream
signin
signup
Create Stream
Create a new file called app/lib/graphql/createStream.graphql
:
mutation CreateStream($input: StreamInput!) {
addStream(input: $input) {
_id
title
description
url
}
}
Note that this mutation has an input value of StreamInput
, which is required.
Edit Stream
Create a new file called app/lib/graphql/editStream.graphql
:
mutation EditStream($input: StreamInput!) {
editStream(input: $input) {
_id
title
description
url
}
}
Delete Stream
Create a new file called app/lib/graphql/deleteStream.graphql
:
mutation DeleteStream($id: ObjectId!) {
deleteStream(streamId: $id)
}
Sign In
Create a new file called app/lib/graphql/signin.graphql
:
mutation SignIn($email: String!, $password: String!) {
login(input: { email: $email, password: $password }) {
user {
_id
email
}
token
}
}
Sign Up
Create a new file called app/lib/graphql/signup.graphql
:
mutation SignUp($email: String!, $password: String!) {
register(input: { email: $email, password: $password }) {
user {
_id
email
}
token
}
}
Test Codegen
Let's take this opportunity to generate the queries and mutations with graphql-let
.
Heads Up! Your GraphQL API server needs to be running in order to run
graphql-let
.
In a separate terminal window, run the API server using npm run dev
, and then try the following command in your app
directory:
npx graphql-let
You should see something like the following output:
ā Parse configuration
[ graphql-let ] 8 .d.ts were generated.
If you encountered any issues with the above commands, verify your local GraphQL API server is running and try again.
link Init Client
With our queries and mutations generated, we can continue to initialize the Apollo Client.
Create a new file, app/lib/apollo.ts
and insert the following:
import { useMemo } from 'react';
import {
ApolloClient,
InMemoryCache,
NormalizedCacheObject,
HttpLink,
} from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
let apolloClient: ApolloClient<NormalizedCacheObject> | undefined;
function createApolloClient() {
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = sessionStorage.getItem('token');
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
},
};
});
const httpLink = new HttpLink({
uri: 'http://localhost:8000/graphql',
credentials: 'include',
});
return new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});
}
export function initializeApollo(initialState: any = null) {
const _apolloClient = apolloClient ?? createApolloClient();
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// get hydrated here
if (initialState) {
_apolloClient.cache.restore(initialState);
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
export function useApollo(initialState: any) {
const store = useMemo(() => initializeApollo(initialState), [initialState]);
return store;
}
In the above code, we connect to the Apollo Client with an HttpLink
. During initialization, we also need to include credentials in order to support GraphQL authentication.
On any Next.js page that uses data fetching methods, we can re-hydrate the Apollo client cache and fetch data from the cache instead of the server. The useApollo
hook will handle initializing and caching once its integrated with a root component, like _app.tsx
.
link Apollo Provider
Make the following changes to app/pages/_app.tsx
:
import { useEffect, useState } from 'react';
import CssBaseline from '@material-ui/core/CssBaseline';
import { ThemeProvider } from '@material-ui/core/styles';
import { ApolloProvider } from '@apollo/client';
import { useApollo } from 'lib/apollo';
import { themeDark, themeLight } from 'lib/theme';
export default function MyApp({ Component, pageProps }) {
const [darkState, setDarkState] = useState(false);
const handleThemeChange = () => {
setDarkState(!darkState);
};
const apolloClient = useApollo(pageProps.initialApolloState);
useEffect(() => {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles && jssStyles.parentNode) {
jssStyles.parentNode.removeChild(jssStyles);
}
}, []);
return (
<ApolloProvider client={apolloClient}>
<ThemeProvider theme={darkState ? themeDark : themeLight}>
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</ApolloProvider>
);
}
link Testing
It's time to fire up the Next.js app and test our new header at http://localhost:3000
npm run dev
If you are still able to see the original landing page, success! We have completed the installation of Apollo Client with Next.js.
Congratulations!
Today we completed the frontend Apollo Client setup. Up next, we start building data-fetching queries and create the authentication flow.