TL;DR
Meta が開発した、Web API のためのクエリ言語。GraphQL Schema で型を定義し、クライアントは単一のエンドポイントに必要なデータだけを問い合わせる。
なぜ必要か
REST API と比べて、速度、柔軟性、開発のしやすさを重視して設計されている。クライアントが必要なデータを指定するので、Over-fetching や Under-fetching を避けられる。モバイル端末の普及で、限られた帯域で効率よく取得したいというニーズが増えたことが背景にある。
また、各 UI コンポーネントが Fragment で必要なデータを宣言し、1 つのクエリにまとめて送れる(Fragment Colocation)。
仕組み
スキーマを独自のスキーマ定義言語(SDL)で書き、独自のクエリ言語でリクエストする。特殊な通信プロトコルは必要とせず、この点が gRPC と大きく違う。リクエストには Query(取得)、Mutation(書き換え)、Subscription(購読)がある。
型システム
スキーマの定義は必須で、型に違反したリクエストやレスポンスはランタイムエラーになる。そのため、スキーマを API ドキュメントとして使え、実装とずれにくい。GraphiQL ではスキーマを使って補完できる。
クエリの機能
- Fragment: クエリの重複部分を切り出して再利用し、DRY にする。
- 変数:
$で始まる変数で外から値を受け取る。変数ごとに型を指定する。 - ディレクティブ:
@で始まる修飾子。スキーマでは@deprecatedで廃止予定を示し、クエリでは@includeと@skipで取得するフィールドを条件で切り替える。
例
fragment PostItem on Post {
title
author {
name
}
}
query IncludeQuery($showRanking: Boolean!) {
post {
...PostItem
ranking @include(if: $showRanking)
}
}type Post {
title: String! @deprecated
body: String! @deprecated(reason: "Use `content` instead.")
content: String! @constraint(maxLength: 1000)
}注意点
クライアントが自由にクエリを組めるので、サーバの負荷を予測しにくい。負荷への対策は必須である。
- 監視: Datadog APM や Sentry で負荷やエラーを監視する。
- 外部向け: Depth Limiting や Query Cost Analysis で、フィールド数、ノード数、深さを制限する。
- 内部向け: Persisted Query や Signed Query で、登録済みのクエリだけを受け付ける。
REST API と違ってキャッシュが複雑になりやすく、学習コストもかかる。@include や @skip はクエリの認知的負荷を上げるので、困ったときの手段と考えるのがよい。
自分の理解
スキーマの定義が必須とはいえ、Code first のアプローチではスキーマを手で書かなくてよい。
当初は N+1 問題が起きやすいことをデメリットに挙げていたが、取り消した。関連する話は GraphQL における N+1 問題の発生しやすさはデメリットなのか にある。
関連
- REST API
- GraphQL Schema
- Resolver
- Fragment Colocation
- GraphQL Code Generator
- Trusted Document
- NestJS
- NestJS で GraphQL サーバを実装する