目次

目次

Next.js+ApolloClientでGraphQLを使う

森川拓
森川拓
最終更新日2024/09/02 投稿日2024/09/02

はじめに

株式会社レコチョクでエンジニアをしている森川です。

GraphQLを使うという話があるのですが、GraphQLってなんぞ?という無知蒙昧人間なので、とりあえず使うことになるNext.jsと合わせてお勉強をすることにしました。 基本的にお勉強が苦手なので、毎度何か始める時はとりあえず動かしてよくわからないところを後から調べて誤魔化しています。 今回もその雰囲気を出すためにとりあえず両方構築からしてみました。 せっかくなので、その過程を残しておきます。

目次

  1. Next.jsとは
  2. Apollo Clientとは
  3. バージョン情報
  4. セットアップ
  5. 実装
  6. 起動
  7. 確認
  8. おまけ:GraphQLのクエリを別ファイルで定義する場合
  9. まとめ
  10. 参考

Next.jsとは

Reactをベースにしたフロントエンドフレームワークです。 バージョン13.4以降からAppRouterを導入できるようになりました。今後はAppRouterが推奨されるみたいです。 SSR, SSGを選んでレンダリングできるので表示が早いです。 詳しくは公式を読むのが一番いいです。

Apollo Clientとは

GraphQL APIをシンプルにクライアント側で操作するためのライブラリです。 Subscription機能により、リアルタイムデータの取得が可能です。

こっちも公式を読むのが一番いいです。

バージョン情報

パッケージ名 バージョン
@apollo/client ^3.10.8
@as-integrations/next ^3.0.0
@graphql-codegen/cli 5.0.2
@graphql-codegen/client-preset 4.3.2
@graphql-codegen/typed-document-node ^5.0.9
@graphql-codegen/typescript-operations ^4.2.3
@types/node ^20
@types/react ^18
@types/react-dom ^18
graphql ^16.9.0
typescript ^5

セットアップ

nodeとかyarnはインストール済みの前提で進めてしまいます…。

Nextjsでプロジェクト作成

npx create-next-app@latest

ごちゃごちゃと聞かれるので、適時自分がビルドしたい内容に合わせて選択します。参考

✔ What is your project named? … sample-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 to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
✔ What import alias would you like configured? … @/*

Apollo Clientの導入

yarn add -D @apollo/client @as-integrations/next graphql @graphql-codegen/cli @graphql-codegen/client-preset @graphql-codegen/typed-document-node @graphql-codegen/typescript-operations
  • @apollo/client
    • Apollo Clientのコアライブラリ
  • @as-integrations/next
    • Next.jsとApollo Clientを統合するためのライブラリ
  • graphql
    • GraphQLのコアライブラリ、スキーマの定義、クエリの解析、実行、結果の検証を行うための機能を提供してくれます。
  • graphql-codegen
    • GraphQLスキーマからコードを生成するライブラリ

codegen.tsの生成

codegen.tsを生成します。

yarn graphql-codegen init
Backend - API or server
  Application built with Angular
❯ Application built with React
  Application built with Stencil
  Application built with Vue
  Application using graphql-request
  Application built with other framework or vanilla JS

どれで作るか聞かれるのでReactを選択します。

? What type of application are you building? Application built with React
? Where is your schema?: (path or url) http://localhost:4000
? Where are your operations and fragments?: src/**/*.tsx
? Where to write the output: src/gql/
? Do you want to generate an introspection file? No
? How to name the config file? codegen.ts
? What script in package.json should run the codegen? codegen

いろいろ聞かれるので自分のプロジェクトに合わせて選択します。

パス等を変更する場合は以下の内容もそれに置き換えて考えてください。

実行するとcodegen.tsが生成されます。 生成されたファイルを開き ignoreNoDocuments: trueを追加します。 →これを追加するとdocmentsのパスにファイルが存在しない場合にエラーを出さないようになります。

<br>import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  overwrite: true,
  schema: "http://localhost:4000",
  documents: "src/**/*.tsx",
  generates: {
    "src/gql/": {
      preset: "client",
      plugins: []
    }
  },
  ignoreNoDocuments: true // これを追加
};

export default config;

このままにするとschemaが存在しないというエラーが出るので今回はフリーのAPIを使ってそれに接続するようにします。 SpaceX API <a href="https://spacex-production.up.railway.app/">https://spacex-production.up.railway.app/</a>

<br>import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  overwrite: true,
  schema: "https://spacex-production.up.railway.app/", // これを変える
  documents: "src/**/*.tsx",
  generates: {
    "src/gql/": {
      preset: "client",
      plugins: []
    }
  },
  ignoreNoDocuments: true
};

export default config;

  • 設定参考 スキーマを自分で定義したり、スキーマごとに別のディレクトリに書き出したりとかもできます。これに関しては後述します。

codegenの実行

package.jsonに codegenが追加されているのでこのコマンドを実行します。

yarn codegen

実装

Apollo Clientの実装

ApolloClientを利用するためにインスタンスを初期化します。

srcの配下に apollo/ディレクトリを作成し、その中にclient.tsを作成します。

import { ApolloClient, InMemoryCache } from "@apollo/client";

export const client = new ApolloClient({
  uri: "https://spacex-production.up.railway.app/",
  cache: new InMemoryCache(),
});

uri: GraphQLサーバーのエンドポイントを指定します。 cache: クエリの結果をキャッシュするための設定です。

ApolloProviderの実装

コンポーネントでApolloClientを利用するためにApolloProviderを作成します。 これで、コンポーネント内でクエリを実行することができます。

appの配下に providers/ディレクトリを作成し、その中にWithApolloProvider.tsxを作成します。

"use client";

import { ApolloProvider } from "@apollo/client";
import React from "react";
import { client } from "../../apollo/client";

export const WithApolloProvider = ({ children }: React.PropsWithChildren) => {
  return <ApolloProvider client={client}>{children}</ApolloProvider>;
};

データを取得してみる

appの配下に components/ディレクトリを作成し、その中にapi/ディレクトリを作成します。 その中に GetLaunches.tsxを作成します。

"use client"

import { gql, useQuery } from "@apollo/client";
import { Launch } from "../../../gql/graphql";

const ALL_LAUNCHES = gql`
    query GetAllLaunches {
        launches {
            mission_name
            rocket {
                rocket_name
            }
        }
    }
    `;

export const GetLaunches = () => {
    const { data, loading, error } = useQuery(ALL_LAUNCHES);
    if (loading) return <div>LOADING...</div>;
    if (error) return <div>{error.message}</div>;
    return (
        <div>
            <ul>
                {data.launches.map((launch: Launch, idx: number) => (
                    <li key={String(idx)}>
                        mission_name: {launch.mission_name}(rocket_name: {launch.rocket?.rocket_name})
                    </li>
                ))}
            </ul>
        </div>
    );
};

データを表示する

import { GetLaunches } from "./components/api/GetLaunches";
import { WithApolloProvider } from "./provider/WithApolloProvider";

export default function Home() {
  return (
    <div>
      <h1>SpaceX Launches</h1>
      <p>---------------------</p>
      <WithApolloProvider>
        <GetLaunches />
      </WithApolloProvider>
    </div>
  );
}

起動

yarn dev

確認

<a href="http://localhost:3000/">http://localhost:3000/</a>にアクセスしてデータが表示されていれば成功です!

おまけ:GraphQLのクエリを別ファイルで定義する場合

.graphqlファイルを作成し、そこにクエリを記述することで、クエリを別ファイルで管理することができます。

src/gql/queryディレクトリを作成し、その中にGetAllLaunches.graphqlを作成します。

query GetAllLaunches {
    launches {
        mission_name
        rocket {
            rocket_name
        }
    }
}

codegen.tsを以下のように変更します。

<br>import type { CodegenConfig } from '@graphql-codegen/cli';
import path from 'path';

const config: CodegenConfig = {
  overwrite: true,
  schema: "https://spacex-production.up.railway.app/",
  documents: path.resolve(__dirname, "src/gql/query/*.graphql"), // 絶対パスにしないとエラーが出る
  generates: {
    "src/gql/": {
      preset: "client",
      plugins: []
    }
  },
  // ignoreNoDocuments: true 有効だとDocmentsが存在しない場合にエラーを出さないのでコメントアウト
};

export default config;

codegenを実行します。

yarn codegen

GetLaunches.tsxを以下のように変更します。

"use client"

import { useQuery } from "@apollo/client";
import { AllLaunchesQueryDocument, Launch } from "../../../gql/graphql";

export const GetLaunches = () => {
    const { data, loading, error } = useQuery(AllLaunchesQueryDocument);
    if (loading) return <div>LOADING...</div>;
    if (error) return <div>{error.message}</div>;
    return (
        <div>
            <ul>
                {data?.launches?.map((launch: Launch | null, idx: number) => (
                    <li key={String(idx)}>
                        mission_name: {launch?.mission_name}(rocket_name: {launch?.rocket?.rocket_name})
                    </li>
                ))}
            </ul>
        </div>
    );
};

確認する。

yarn dev

まとめ

スキーマはバックエンド側で定義されているものを使うことになると思うので、そのスキーマを元にクエリを定義していくことになることが多いかなと思います。 なので、バックと共通のファイルを利用することでフロントエンドは型や返却値を意識せず、都度codegenで定義ファイルを作成し、それを利用することで呼び出しなどに無駄なリソースを割かずに開発を進めていくことができそうです。

RESTAPIを使っていると、型定義を書くのが面倒だったり、型が合わないとかでエラーが出ることが多いですが、GraphQLを使うことでこれらの問題を解決することができるので、GraphQLを使っていくことで開発効率が上がることが期待できるかなと思います。

Subscriptionなども実際の開発では使っていくことにはなるのかなと思いますが、Docsも豊富だったので、今回のようにお手軽に実装していけるのではないかなーと思っています。

最後まで閲覧いただきありがとうございました。

参考

森川拓

目次