Masayan tech blog.

  1. ブログ記事一覧>
  2. 【2024年最新】Next.js 14、React.js 18 環境でGraphQLを使用した環境構築手順

【2024年最新】Next.js 14、React.js 18 環境でGraphQLを使用した環境構築手順

公開日

本記事を読むとできるようになること

  • ローカルでGraphQLサーバーを起動する方法が理解できる
  • フロントエンドからGraphQLサーバーへリクエストを送信する方法が理解できる
  • React Server ComponentでGraphQLを利用するために必要な設定について理解できる
  • GraphQL Code Generatorを使用して、スキーマに沿った型定義ファイルを自動生成する方法が理解できる

前提として、Next 14でプロジェクトを作成するところまではできているものとして進めます

1. ローカルでGraphQLサーバーを作る

Node.jsでGraphQLサーバーを作る

プロジェクト初期化 & 必要パッケージをインストール

npm init -y
npm install @apollo/server graphql

リゾルバを適当に定義する

index.js

const { ApolloServer, gql } = require("apollo-server");

const typeDefs = gql`
  type User {
    id: ID!
    name: String!
    age: Int!
  }

  type Query {
    user(id: ID!): User
    users: [User]
  }
`;

const users = [
  {
    id: "1",
    name: "Bob",
    age: 31,
  },
  {
    id: "2",
    name: "John",
    age: 19,
  },
];

const resolvers = {
  Query: {
    user: (parent, args) => {
      const user = users.find((user) => user.id === args.id);
      return user;
    },
    users: () => users,
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

server.listen().then(({ url }) => {
  console.log(`Server ready at ${url}`);
});

サーバー起動

node server.js

Server ready at http://localhost:4000/ と表示されるので、http://localhost:4000/にアクセスすると、GraphQLのsandboxが起動する

2.フロントエンドからGraphQLサーバーにリクエストを送信できるようにする

RSCでもapolloを使用できるような前提で進める。

必要パッケージをインストール

npm i -D graphql @apollo/client @apollo/experimental-nextjs-app-support

Apollo clientを使用してリクエストを送信する場合、useQueryを使うことになるが、useQueryは現状クライアントサイドでしか使用できないため、そのまま使うと

React functionality 'useContext' is not available in this environment [Nextjs app] と怒られてしまう

RSCでuseQueryを使用するには、以下のissueにも記述がある通り、「@apollo/experimental-nextjs-app-support」を使用する必要がある

https://github.com/apollographql/apollo-client-nextjs#react-server-components

続いて、Next14からのApp routerでは以下の手順になる

  1. ApolloWrapperを作成(これは"use client"宣言が必要)
  2. ApolloWrapperでアプリケーションをラップする(app/layout.tsxに記載)
  3. 任意のサーバーコンポーネントでクエリを実行する("use client"宣言なし ⇦これが今回したいこと

ApolloWrapper.tsx

"use client";

import { ApolloLink, HttpLink } from "@apollo/client";
import {
  ApolloNextAppProvider,
  ApolloClient,
  InMemoryCache,
  SSRMultipartLink,
} from "@apollo/experimental-nextjs-app-support";

function makeClient() {
  const httpLink = new HttpLink({
    uri: "http://localhost:4000/graphql",
    fetchOptions: { cache: "no-store" },
  });

  return new ApolloClient({
    cache: new InMemoryCache(),
    link: httpLink,
  });
}

export function ApolloWrapper({ children }: React.PropsWithChildren) {
  return (
    <ApolloNextAppProvider makeClient={makeClient}>
      {children}
    </ApolloNextAppProvider>
  );
}

app/layout.tsx にApolloWrapperをimportしてラップする

import { ApolloWrapper } from "@/features/auth/ApolloWrapper";
...
export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="ja">
      <ApolloWrapper>
        <UserProvider>
          <body className={inter.className}>
            <header>ヘッダー</header>
            {children}
            <footer>フッター</footer>
          </body>
        </UserProvider>
      </ApolloWrapper>
    </html>
  );
}

ApolloWrapperでラップされている何かしらのページの中でサーバーコンポーネントを用意し、ApolloWrapper.tsxで定義したgetClientを呼び出した上でクエリを実行する。
こうすることで、サーバーコンポーネント上でapolloを使用してリクエストを送信することが可能になる

import { GET_USERS } from "@/gql/queries/userQueries";
import { getClient } from "@/lib/apolloClient";

export async function UserList() {
  const { data, loading, error } = await getClient().query({
    query: GET_USERS,
  });

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

...

3.クエリの型定義自動生成

GraphQLで定義したクエリの返り値とか、クエリ自体の型定義を、APIのスキーマ定義とフロントエンドのクエリ定義から自動生成できるようにしたいので、その手順を解説。ReactとApollo Clientを使用している場合は、Apollo ClientのuseQueryに渡すためのオブジェクトを自動で生成してくれるようにしたい

// こういうのとか
query: gql`
  query Users {
    users {
      id
      name
      age
    }
  }
 `
// ↑の返り値の型をtypescriptで手動で定義したくない

GraphQL Code Generator (graphql-codegen) を使用することで、GraphQL スキーマ (schema.graphqls) とgql``などのクエリ用のドキュメントをインプットとして与えることで、型情報を生成できる

依存関係のインストール

npm i -D @graphql-codegen/cli

config.tsを作成(ymlでも可)

以下コマンドを実行。後で修正するので、基本yesでOK

npx graphql-codegen init

? 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?: ./**/*.{ts,tsx}
? Where to write the output: 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

雛形が生成されるので、プロジェクトのディレクトリ構成などに合わせて微調整(特にschemaやdocumentsの値)

codegen.ts

import type { CodegenConfig } from "@graphql-codegen/cli";

const config: CodegenConfig = {
  overwrite: true,
  // バックエンドのGraphQLサーバーがスキーマのエンドポイントを持っているので、そのURLを指定
  schema: "http://localhost:4000/graphql",
  // クエリやミューテーションを使用しているファイルのパスを指定
  documents: "./**/*.{ts,tsx}",

  // 型定義ファイルなどがこのディレクトリに出力される
  // キーには、生成されるファイルのパスを指定
  generates: {
    "gql/": {
      // このプリセットを指定するだけで個別のGraphQLクライアント向けプラグイン(typescript-react-apolloなど)が不要になる
      // 詳細はこちら(https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#installation に書いてあるようなフロントエンドのライブラリの内容に対応する
      preset: "client",
      // presetを使用する場合は、pluginsは使用できないので注意。(こちらに'typescript', 'typescript-operations',とか定義すると、重複して生成されてしまう
      plugins: [],
    },
  },
};

export default config;

graphql-codegen init コマンド実行により、package.json を更新することがあるので、依存パッケージをインストール

npm install

init時に、package.jsonにコマンド追加されている

 "scripts": {
    ...
    "codegen": "graphql-codegen --config codegen.ts"

以下を実行。codegen.tsで参照しているスキーマと、GraphQL操作文字列を含むコードが定義されているファイルをもとに、型情報を生成

npm run codegen

もし、スキーマとクエリに型の不整合があればエラーになるので、スキーマに違反するようなクエリを書くことができないという強力なメリットがある

gqlディレクトリにgql/graphql.tsが生成され、この中にはクエリの型定義などが含まれているので、Apollo Client が提供する gql 関数の代わりに、自動生成された graphql 関数を使ってクエリ(ドキュメント)を定義する

import { graphql } from "@/gql";
import { UserQuery } from "@/gql/graphql";
import { getClient } from "@/lib/apolloClient";

export class UserRepository {
  static async searchUsers() {
    // gql → graphqlに変える
    const UsersQueryDocument = graphql(
      `
        query Users {
          users {
            id
            name
            age
          }
        }
      `
    );

    const { data, loading, error } = await getClient().query({
      query: UsersQueryDocument,
    });

    return { users: data.users, loading, error };
  }

graphql関数の返り値は TypedDocumentNode という型のオブジェクトであり、useQueryに引数として指定すると、返り値のdataオブジェクトが自動的に型付けされる

※graphql関数に渡す文字列リテラルを 1 文字でも変えたら、再度graphql-codegen によるコードの再生成が必要になる

まとめ

いかがでしたでしょうか。本記事では【2024年最新】Next.js 14、React.js 18 環境でGraphQLを使用した環境構築手順について紹介しています。ハマりがちなRSCの設定や効率化のための自動生成の方法についても具体的説明していますので、ぜひ参考にしてみてください。