Next.js × Apollo Client で GraphQL API をコールする

# 概要

この記事では、`Apollo Client` を活用して` Next.js` アプリケーションから `GraphQL` サーバーのデータを取得・表示するまでの一連の流れを解説します。

# 全体フロー

まずは、クライアントから `GraphQL` サーバーへデータを取得するまでの流れを確認しておきましょう。

```mermaid
sequenceDiagram
    participant Component as Component
    participant Fetcher as Fetcher
    participant Apollo as Apollo Client
    participant Cache as Apollo Cache
    participant GraphQL as GraphQL Server
    
    Component->>Fetcher: データ取得関数
    Note over Component,Fetcher: Variables
    Fetcher->>Apollo: apolloClient.query
    Note over Fetcher,Apollo: Variables
    
    Apollo->>Cache: キャッシュ確認
    
    alt キャッシュあり
        Cache->>Apollo: キャッシュデータ返却
    else キャッシュなし
        Apollo->>GraphQL: Request GraphQL
        GraphQL->>Apollo: データ返却
        Apollo->>Cache: キャッシュ保存
    end
    
    Apollo->>Fetcher: 正規化データ返却
    Fetcher->>Component: 整形済みデータ返却
    Component->>Component: データでレンダリング
```

# 実装フロー

## 1.Apollo 関連の依存関係インストール

まずは `Apollo Client` や `GraphQL` 関連のライブラリをインストールします。

```bash
npm install graphql @apollo/client @apollo/server @as-integrations/next
```

## 2. GraphQL Code Generator の依存関係インストール

型安全な開発のために、`GraphQL Code Generator` を導入します。
これにより、スキーマから`TypeScript`型を自動生成できます。

```bash
npm install -D @graphql-codegen/cli @graphql-codegen/client-preset @graphql-codegen/typescript @graphql-codegen/typescript-resolvers @graphql-codegen/typescript-document-nodes
```

## 3. Codegenの設定
GraphQLスキーマから型を自動生成できるように、Codegenの設定を実施します。

```yml:codegen.yml
schema: http://${NEXT_PUBLIC_API_DOMAIN}/graphql
documents: './src/**/*.ts'
generates:
  ./src/app/graphql/generated.ts:
    plugins:
      - typescript
      - typescript-operations
      - typed-document-node
    config:
      scalars:
        ISO8601DateTime: string
```

```json:package.json
{
  "scripts": {
    "codegen": "dotenv -- graphql-codegen --config codegen.yml"
  },
}
```

設定完了後、実行します。
```
npm run codegen
```

### 参考記事
https://zenn.dev/merutin/articles/f7bc5ba9a636b5

https://qiita.com/yoshii0110/items/b461e608dc0cff78982e

## 4. Apollo Clientの設定（apollo-client.ts）

`GraphQL` サーバーへの接続設定を `apollo-client.ts` にまとめます。

```typescript:src/app/graphql/apollo-client.ts
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';

const httpLink = new HttpLink({
  uri: `http://${process.env.NEXT_API_DOMAIN}/graphql`,
  credentials: 'include',
  fetchOptions: {
    mode: 'cors',
  }
});

export const apolloClient = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache(),
  defaultOptions: {
    query: {
      fetchPolicy: 'cache-first',
    },
    mutate: {
      fetchPolicy: 'no-cache',
    },
  },
  connectToDevTools: process.env.NODE_ENV !== 'production',
});
```

:::note warn
クロスサイトのリクエスト時、以下のオプションが含まれていないとCookieが送信されないため指定。
より良い方法があればご教示いただきたいです🙏

```typescript
  credentials: 'include',
  fetchOptions: {
    mode: 'cors',
  }
```
:::

## 5. データフェッチャーの実装（fetcher.ts）

`GraphQL` クエリを実行するラッパー関数を `fetcher.ts` に実装します。

```typescript
import { apolloClient } from '@/app/graphql/apollo-client';
import { GetPostDocument } from '@/app/graphql';

export async function getPosts(page = 1, perPage = 15) {
  const { data } = await apolloClient.query({
    query: GetPostsDocument,
    variables: { page, perPage },
    context: {
      headers: {
        Cookie: cookieHeader,
      },
    },
  });
  return { json: () => Promise.resolve(data.publishedPosts as Post[]) };
};
```

:::note warn
クロスサイトのリクエスト時、以下のオプションが含まれていないとCookieが送信されないため指定。
より良い方法があればご教示いただきたいです🙏
（`ApolloClient` で一括指定したかった...）

```typescript
    context: {
      headers: {
        Cookie: cookieHeader,
      },
    },
```
:::

## 6. コンポーネントでのデータ取得と表示

最後に、取得したデータをコンポーネント内で表示します。

```tsx
import { getPosts } from "@/app/(public)/posts/fetcher";

export default async function Home() {
  const posts = await getPosts();
  const postsJson = await posts.json();

  return (
    <div>
      <h1>記事一覧</h1>
      
      <ul>
        {postsJson.map((post) => (
          <li key={post.id}>
            <h2>{post.title}</h2>
            <p>{post.content.substring(0, 50)}...</p>
            <a href={`/posts/${post.id}`}>詳細を見る</a>
          </li>
        ))}
      </ul>
      
      {postsJson.length === 0 && <p>記事がありません</p>}
    </div>
  );
}
```

# まとめ

この記事では、Next.js と Apollo Client を用いて GraphQL サーバーからデータを取得し、コンポーネントで表示するまでの流れを解説しました。

- Apollo Clientでの通信設定
- GraphQLクエリの定義と実行
- フェッチャーを挟んだ責務分離