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:

  1. currentUser
  2. stream
  3. 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:

  1. createStream
  2. editStream
  3. deleteStream
  4. signin
  5. 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.

link References

format_list_bulleted
help_outline