# 機能設計書 12-GraphQL API

## 概要

本ドキュメントは、Payload CMSにおけるGraphQL API機能の設計を定義する。コレクションおよびグローバルデータに対するGraphQLスキーマの自動生成、クエリ・ミューテーション操作を提供し、型安全なデータアクセスを実現する。

### 本機能の処理概要

GraphQL APIは、Payload CMSの設定ファイルに定義されたコレクションとグローバルの構造を基に、GraphQLスキーマを自動生成する機能である。クライアントは必要なフィールドのみを指定してデータを取得でき、オーバーフェッチ・アンダーフェッチを防止できる。

**業務上の目的・背景**：REST APIでは複数のエンドポイントへのリクエストが必要な場合や、不要なデータも含めて取得してしまうケースがある。GraphQL APIを提供することで、フロントエンド開発者が必要なデータを1回のリクエストで効率的に取得でき、ネットワーク帯域の最適化とクライアントパフォーマンスの向上を実現する。また、スキーマによる型定義によりAPI仕様の明確化と開発時の型安全性を提供する。

**機能の利用シーン**：
- 複数のリレーションを含む複雑なデータ取得
- フロントエンドアプリケーションでの効率的なデータフェッチ
- GraphQL PlaygroundやGraphiQLを使用したAPI探索
- 型定義を活用した型安全なクライアント開発
- リアルタイムなデータ取得ニーズへの対応

**主要な処理内容**：
1. スキーマ自動生成：コレクション/グローバル設定からGraphQLスキーマを動的生成
2. クエリ操作：find（一覧）、findByID（単一）、count（件数）、versions（バージョン）
3. ミューテーション操作：create、update、delete、duplicate、restoreVersion
4. 認証操作：login、logout、refresh、me、forgotPassword、resetPassword、verifyEmail、unlock
5. クエリ複雑度制御：最大複雑度によるDoS攻撃防止

**関連システム・外部連携**：
- PayloadのOperations層（REST APIと共通のビジネスロジック）
- データベースアダプター経由のデータ操作
- 認証システムとの統合

**権限による制御**：
- コレクション設定の`access`オブジェクトによる権限制御
- `docAccess`クエリによる特定ドキュメントへのアクセス権限確認
- `graphQL.disableQueries` / `graphQL.disableMutations`による操作の無効化

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 17 | APIビュー画面 | 補助機能 | GraphQLレスポンス形式での表示（対応時） |

## 機能種別

CRUD操作 / APIエンドポイント / スキーマ自動生成

## 入力仕様

### 入力パラメータ

#### クエリ共通パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| locale | LocaleInputType | No | 取得するロケール | 設定されたロケールコード |
| fallbackLocale | FallbackLocaleInputType | No | フォールバックロケール | 設定されたロケールコード |
| draft | Boolean | No | ドラフトを含めるか | true/false |
| trash | Boolean | No | ゴミ箱のドキュメントを含めるか | true/false |

#### find（一覧取得）クエリ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| where | WhereInputType | No | フィルタ条件 | 自動生成されたwhere型 |
| sort | String | No | ソート条件（カンマ区切り） | フィールド名 |
| limit | Int | No | 取得件数上限 | 0以上の整数 |
| page | Int | No | ページ番号 | 1以上の整数 |
| pagination | Boolean | No | ページネーション有効化 | true/false |
| select | Boolean | No | フィールド選択モード | true/false |

#### findByID（単一取得）クエリ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | ID | Yes | ドキュメントID | NonNull |

#### create（作成）ミューテーション

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| data | MutationInputType | Yes | 作成データ | 自動生成された入力型 |
| draft | Boolean | No | ドラフトとして保存 | true/false |

#### update（更新）ミューテーション

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | ID | Yes | 更新対象のドキュメントID | NonNull |
| data | UpdateMutationInputType | Yes | 更新データ | 自動生成された入力型 |
| autosave | Boolean | No | 自動保存モード | true/false |
| draft | Boolean | No | ドラフトとして保存 | true/false |

### 入力データソース

- GraphQLリクエストボディ（query/mutation文字列）
- 変数（variables）
- リクエストヘッダー（認証トークン等）

## 出力仕様

### 出力データ

#### 単一ドキュメント取得時

| 項目名 | 型 | 説明 |
|--------|-----|------|
| (フィールド) | 各型 | スキーマに定義されたフィールド |
| id | ID | ドキュメントID |
| createdAt | DateTime | 作成日時 |
| updatedAt | DateTime | 更新日時 |

#### 一覧取得時（PaginatedListType）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| docs | [ObjectType] | ドキュメント配列 |
| totalDocs | Int | 総ドキュメント数 |
| limit | Int | 取得件数上限 |
| totalPages | Int | 総ページ数 |
| page | Int | 現在のページ番号 |
| pagingCounter | Int | ページングカウンター |
| hasPrevPage | Boolean | 前ページ存在フラグ |
| hasNextPage | Boolean | 次ページ存在フラグ |
| prevPage | Int | 前ページ番号 |
| nextPage | Int | 次ページ番号 |

#### count取得時

| 項目名 | 型 | 説明 |
|--------|-----|------|
| totalDocs | Int | 総ドキュメント数 |

### 出力先

- GraphQLレスポンス（JSON形式、dataフィールド内）
- errorsフィールド（エラー発生時）

## 処理フロー

### 処理シーケンス

```
1. 設定読み込み・スキーマ生成
   └─ configToSchema()でSanitizedConfigからGraphQLSchemaを生成

2. GraphQLリクエスト受信
   └─ graphql-httpライブラリがリクエストを処理

3. バリデーションルール適用
   └─ クエリ複雑度チェック、本番環境でのイントロスペクション無効化

4. リゾルバー実行
   └─ 対応するリゾルバー関数を呼び出し

5. Operations層呼び出し
   └─ findOperation、createOperation等の共通ロジックを実行

6. レスポンス生成
   └─ GraphQL形式でレスポンスを構築

7. レスポンス返却
   └─ dataまたはerrorsを含むJSONを返却
```

### フローチャート

```mermaid
flowchart TD
    A[設定ファイル読み込み] --> B[configToSchema実行]
    B --> C[initCollections]
    B --> D[initGlobals]
    C --> E[コレクションスキーマ生成]
    D --> F[グローバルスキーマ生成]
    E --> G[GraphQLSchema生成]
    F --> G
    G --> H[リクエスト受信]
    H --> I[バリデーションルール適用]
    I --> J{複雑度チェック}
    J -->|OK| K[リゾルバー実行]
    J -->|NG| L[エラーレスポンス]
    K --> M[Operations層呼び出し]
    M --> N[レスポンス生成]
    N --> O[レスポンス返却]
    L --> O
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 複雑度制限 | クエリ複雑度がmaxComplexityを超えた場合はエラー | 全クエリ |
| BR-02 | イントロスペクション制限 | 本番環境では__schema、__typeクエリを無効化可能 | disableIntrospectionInProduction有効時 |
| BR-03 | クエリ/ミューテーション無効化 | コレクション単位でクエリまたはミューテーションを無効化可能 | graphQL.disableQueries/disableMutations設定時 |
| BR-04 | 単数形/複数形名称 | コレクションslugから自動生成、カスタム指定も可能 | スキーマ生成時 |
| BR-05 | 名称衝突回避 | 単数形と複数形が同じ場合は"all"プレフィックス付加 | Media等の名称 |
| BR-06 | リゾルバーdepth=0 | GraphQLリゾルバーは常にdepth=0で呼び出し | 全リゾルバー |

### 計算ロジック

**クエリ複雑度計算**:
- `simpleEstimator`: 各フィールドにデフォルト複雑度1を設定
- `fieldExtensionsEstimator`: フィールド定義の`complexity`拡張を使用
- 合計複雑度が`maxComplexity`を超えた場合はリクエスト拒否

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| {Collection}（単一取得） | {collection} | SELECT | findByIDOperationを呼び出し |
| {Collections}（一覧取得） | {collection} | SELECT | findOperationを呼び出し |
| count{Collections} | {collection} | SELECT | countOperationを呼び出し |
| create{Collection} | {collection} | INSERT | createOperationを呼び出し |
| update{Collection} | {collection} | UPDATE | updateByIDOperationを呼び出し |
| delete{Collection} | {collection} | DELETE | deleteByIDOperationを呼び出し |
| duplicate{Collection} | {collection} | INSERT | duplicateOperationを呼び出し |

### テーブル別操作詳細

Operations層（REST APIと共通）に委譲するため、詳細はREST API機能設計書を参照。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| GRAPHQL_PARSE_ERROR | 構文エラー | クエリの構文が不正 | クエリ構文を確認 |
| GRAPHQL_VALIDATION_ERROR | バリデーションエラー | スキーマに存在しないフィールド参照 | スキーマを確認 |
| COMPLEXITY_LIMIT_EXCEEDED | 複雑度超過 | クエリ複雑度がmaxComplexityを超過 | クエリを分割または簡略化 |
| INTROSPECTION_DISABLED | イントロスペクション無効 | 本番環境で__schemaまたは__typeを参照 | イントロスペクションは開発環境で実行 |
| FORBIDDEN | 権限エラー | アクセス権限なし | 権限を確認 |

### リトライ仕様

GraphQLのエラーはレスポンスの`errors`フィールドに含まれて返却される。クライアント側でのリトライ判断が必要。

## トランザクション仕様

ミューテーション操作はOperations層のトランザクション制御に従う。単一のミューテーションは1トランザクション内で実行される。

## パフォーマンス要件

- クエリ複雑度制限によるDoS攻撃防止
- `depth=0`設定によるN+1問題の回避（リレーション展開はクライアントが明示的に指定）
- `select`パラメータによるフィールド選択でデータ転送量最適化

## セキュリティ考慮事項

- `maxComplexity`設定によるクエリ複雑度制限
- 本番環境でのイントロスペクション無効化オプション
- コレクション/フィールドレベルのアクセス制御
- 認証トークンによるリクエスト認証

## 備考

- GraphQLエンドポイントはデフォルトで`/api/graphql`
- カスタムクエリ/ミューテーションは`config.graphQL.queries`/`config.graphQL.mutations`で追加可能
- GraphQL Playgroundは`config.graphQL.schemaOutputFile`でスキーマファイル出力可能

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、GraphQLスキーマの構造とリゾルバーの型定義を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types.ts | `packages/graphql/src/resolvers/types.ts` | Context型、リゾルバー共通型の定義 |
| 1-2 | types.ts | `packages/graphql/src/exports/types.ts` | 外部公開される型定義 |

**読解のコツ**: GraphQLリゾルバーはPayloadRequestを含むContextを受け取り、PayloadのOperations層を呼び出すラッパー関数として機能する。

#### Step 2: エントリーポイントを理解する

スキーマ生成のメイン関数を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/graphql/src/index.ts` | configToSchema関数がスキーマ生成のエントリーポイント |

**主要処理フロー**:
1. **23-29行目**: コレクション設定をオブジェクトに変換
2. **35-53行目**: graphqlResult初期化（Query、Mutation、types）
3. **55-60行目**: ローカリゼーション設定時にロケール入力型を生成
4. **62-63行目**: initCollections、initGlobalsでスキーマを構築
5. **65-68行目**: Accessクエリを追加
6. **70-96行目**: カスタムクエリ/ミューテーションを追加
7. **98-104行目**: GraphQLSchemaオブジェクトを生成
8. **106-120行目**: バリデーションルール（複雑度チェック等）を生成

#### Step 3: スキーマ生成処理を理解する

コレクションからGraphQL型を生成する処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | initCollections.ts | `packages/graphql/src/schema/initCollections.ts` | コレクションスキーマ生成 |
| 3-2 | buildObjectType.ts | `packages/graphql/src/schema/buildObjectType.ts` | オブジェクト型生成 |
| 3-3 | buildWhereInputType.ts | `packages/graphql/src/schema/buildWhereInputType.ts` | WHERE条件型生成 |
| 3-4 | buildMutationInputType.ts | `packages/graphql/src/schema/buildMutationInputType.ts` | ミューテーション入力型生成 |
| 3-5 | buildPaginatedListType.ts | `packages/graphql/src/schema/buildPaginatedListType.ts` | ページネーション型生成 |

**主要処理フロー**（initCollections.ts）:
- **52-86行目**: 各コレクションの単数形/複数形名称を決定
- **111-120行目**: buildObjectTypeでGraphQLObjectTypeを生成
- **122行目**: buildPaginatedListTypeでページネーション型を生成
- **124-128行目**: buildWhereInputTypeでWHERE条件型を生成
- **164-174行目**: 作成用MutationInputTypeを生成
- **176-189行目**: 更新用MutationInputTypeを生成
- **196-267行目**: クエリフィールド（find、findByID、count、docAccess）を登録
- **269-326行目**: ミューテーションフィールド（create、update、delete、duplicate）を登録
- **328-413行目**: バージョン管理用のクエリ/ミューテーションを登録
- **415-584行目**: 認証コレクション用のクエリ/ミューテーション（me、login、logout等）を登録

#### Step 4: リゾルバー実装を理解する

各GraphQL操作を処理するリゾルバーの実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | find.ts | `packages/graphql/src/resolvers/collections/find.ts` | 一覧取得リゾルバー |
| 4-2 | create.ts | `packages/graphql/src/resolvers/collections/create.ts` | 作成リゾルバー |
| 4-3 | update.ts | `packages/graphql/src/resolvers/collections/update.ts` | 更新リゾルバー |
| 4-4 | delete.ts | `packages/graphql/src/resolvers/collections/delete.ts` | 削除リゾルバー |

**主要処理フロー**（find.ts）:
- **30-31行目**: findResolver関数がResolverを返す
- **32-36行目**: リクエストオブジェクトを分離（locale、fallbackLocale、transactionID）
- **37行目**: select引数がtrueならGraphQL infoからselect構造を構築
- **39-40行目**: localeとfallbackLocaleをargsから設定
- **55-67行目**: findOperation呼び出し用のオプションを構築
- **69行目**: findOperationを呼び出して結果を取得

**主要処理フロー**（create.ts）:
- **28行目**: createResolver関数がResolverを返す
- **29-31行目**: locale設定
- **33-39行目**: createOperationを呼び出し（depth=0で固定）

#### Step 5: 複雑度制御を理解する

クエリ複雑度のバリデーションルールを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | createComplexityRule.ts | `packages/graphql/src/packages/graphql-query-complexity/createComplexityRule.ts` | 複雑度ルール生成 |
| 5-2 | QueryComplexity.ts | `packages/graphql/src/packages/graphql-query-complexity/QueryComplexity.ts` | 複雑度計算ロジック |

**主要処理フロー**:
- `simpleEstimator`: デフォルト複雑度1を各フィールドに設定
- `fieldExtensionsEstimator`: フィールドのcomplexity拡張を参照
- 合計複雑度が`maximumComplexity`を超えた場合はGraphQLErrorを投げる

### プログラム呼び出し階層図

```
GraphQLリクエスト
    │
    ├─ graphql-http (リクエスト処理)
    │
    ├─ ValidationRules (バリデーション)
    │      ├─ createComplexityRule
    │      │      ├─ simpleEstimator
    │      │      └─ fieldExtensionsEstimator
    │      └─ NoProductionIntrospection
    │
    └─ Resolvers (リゾルバー実行)
           │
           ├─ findResolver
           │      └─ findOperation (payload)
           │
           ├─ findByIDResolver
           │      └─ findByIDOperation (payload)
           │
           ├─ createResolver
           │      └─ createOperation (payload)
           │
           ├─ updateResolver
           │      └─ updateByIDOperation (payload)
           │
           └─ deleteResolver
                  └─ deleteByIDOperation (payload)
```

### データフロー図

```
[設定ファイル]                [スキーマ生成]              [GraphQLスキーマ]

SanitizedConfig ───▶ configToSchema() ───▶ GraphQLSchema
      │                      │                    │
      │                      ▼                    ▼
      │               initCollections()     Query/Mutation
      │                      │              ObjectTypes
      │                      ▼
      │               buildObjectType()
      │               buildWhereInputType()
      │               buildMutationInputType()
      │
      ▼
[リクエスト処理]           [リゾルバー]              [レスポンス]

GraphQL Request ───▶ findResolver ───▶ { data: {...} }
      │                   │
      │                   ▼
      │            findOperation()
      │                   │
      │                   ▼
      │            payload.db.find()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/graphql/src/index.ts` | ソース | スキーマ生成エントリーポイント |
| initCollections.ts | `packages/graphql/src/schema/initCollections.ts` | ソース | コレクションスキーマ初期化 |
| initGlobals.ts | `packages/graphql/src/schema/initGlobals.ts` | ソース | グローバルスキーマ初期化 |
| buildObjectType.ts | `packages/graphql/src/schema/buildObjectType.ts` | ソース | オブジェクト型生成 |
| buildWhereInputType.ts | `packages/graphql/src/schema/buildWhereInputType.ts` | ソース | WHERE条件型生成 |
| buildMutationInputType.ts | `packages/graphql/src/schema/buildMutationInputType.ts` | ソース | ミューテーション入力型生成 |
| buildPaginatedListType.ts | `packages/graphql/src/schema/buildPaginatedListType.ts` | ソース | ページネーション型生成 |
| buildPoliciesType.ts | `packages/graphql/src/schema/buildPoliciesType.ts` | ソース | ポリシー型生成 |
| fieldToSchemaMap.ts | `packages/graphql/src/schema/fieldToSchemaMap.ts` | ソース | フィールド→スキーマ変換マップ |
| find.ts | `packages/graphql/src/resolvers/collections/find.ts` | ソース | 一覧取得リゾルバー |
| findByID.ts | `packages/graphql/src/resolvers/collections/findByID.ts` | ソース | 単一取得リゾルバー |
| create.ts | `packages/graphql/src/resolvers/collections/create.ts` | ソース | 作成リゾルバー |
| update.ts | `packages/graphql/src/resolvers/collections/update.ts` | ソース | 更新リゾルバー |
| delete.ts | `packages/graphql/src/resolvers/collections/delete.ts` | ソース | 削除リゾルバー |
| count.ts | `packages/graphql/src/resolvers/collections/count.ts` | ソース | カウントリゾルバー |
| duplicate.ts | `packages/graphql/src/resolvers/collections/duplicate.ts` | ソース | 複製リゾルバー |
| login.ts | `packages/graphql/src/resolvers/auth/login.ts` | ソース | ログインリゾルバー |
| logout.ts | `packages/graphql/src/resolvers/auth/logout.ts` | ソース | ログアウトリゾルバー |
| me.ts | `packages/graphql/src/resolvers/auth/me.ts` | ソース | 現在ユーザー取得リゾルバー |
| createComplexityRule.ts | `packages/graphql/src/packages/graphql-query-complexity/createComplexityRule.ts` | ソース | 複雑度ルール生成 |
| types.ts | `packages/graphql/src/resolvers/types.ts` | ソース | リゾルバー型定義 |
