ひとことで言うと#
REST APIの「エンドポイントごとに固定レスポンス」という制約から解放され、クライアントが必要なフィールドだけを宣言的に指定して取得できるクエリ言語とランタイム。過不足のないデータ取得が実現できる。
押さえておきたい用語#
- スキーマ(Schema)
- GraphQL APIの型定義と構造を宣言する設計図を指す。SDL(Schema Definition Language)で記述し、フロントエンドとバックエンドの契約として機能する。
- リゾルバー(Resolver)
- スキーマの各フィールドに対応するデータ取得ロジックの関数のこと。「このフィールドのデータをどこから取ってくるか」を定義する。
- N+1問題
- 親レコードN件に対して子レコードをN回個別にDBクエリするパフォーマンス上の問題のこと。DataLoaderパターンでバッチ取得して解消する。
- Mutation(ミューテーション)
- データの作成・更新・削除を行う操作のこと。RESTのPOST/PUT/DELETEに相当する。Queryが読み取り、Mutationが書き込み。
- クエリコスト(Query Complexity)
- 1つのGraphQLクエリが消費するサーバーリソースの見積もり値である。悪意あるクエリからサーバーを守るために上限を設定する。
GraphQL設計原則の全体像#
こんな悩みに効く#
- REST APIのレスポンスが「過剰(使わないフィールドが多い)」か「不足(複数APIを叩く必要がある)」
- 画面ごとに専用のAPIエンドポイントが増え続けて管理しきれない
- モバイルとWebで必要なデータが異なり、1つのAPIで対応しきれない
基本の使い方#
スキーマ(型定義)を最初に設計し、フロントエンド・バックエンドの契約とする。
- GraphQL SDL(Schema Definition Language)で型を定義する
- Query(読み取り)、Mutation(書き込み)、Subscription(リアルタイム)を分ける
- ドメインモデルに基づいた直感的な型名・フィールド名をつける
ポイント: スキーマはAPIの「仕様書」。これをベースにフロントエンドとバックエンドが並行開発できる。
各フィールドの**データ取得ロジック(リゾルバー)**を実装する。
- 型の各フィールドにリゾルバー関数を紐づける
- DataLoaderパターンでN+1問題を防止する
- リゾルバーの中にビジネスロジックを書きすぎない(薄いレイヤーに保つ)
ポイント: リゾルバーは「データをどこから取ってくるか」の責務だけ持たせる。
GraphQL特有のリスクに対策する。
- クエリの深さ制限(depth limiting)で再帰的クエリを防ぐ
- クエリコストの見積もり(query complexity analysis)で重いクエリを制限
- 認証・認可はリゾルバーレベルでフィールド単位に適用する
ポイント: GraphQLは自由度が高い分、悪意あるクエリへの防御が必要。
後方互換性を保ちながらスキーマを進化させる。
- フィールドの追加は非破壊的変更(安全)
- フィールドの削除は
@deprecatedで段階的に廃止 - バージョニングではなく、スキーマの進化(evolution)で対応する
ポイント: RESTのv1/v2のようなバージョン管理は不要。スキーマを育てていく考え方。
具体例#
状況: MAU 200万人のSNSアプリ。タイムライン表示にREST APIを使っており、1画面の表示に平均3.2回のAPIコールが必要だった。
RESTの場合: タイムラインを表示するのに /posts → /users/{id} → /posts/{id}/comments と3回APIを叩く必要がある。
GraphQLの場合:
query Timeline {
posts(first: 20) {
id
content
createdAt
author {
name
avatarUrl
}
comments(first: 3) {
content
author { name }
}
}
}1リクエストで、投稿・著者情報・コメントを必要なフィールドだけ取得。
| 指標 | REST API | GraphQL |
|---|---|---|
| APIコール数/画面 | 3.2回 | 1回 |
| データ転送量 | 148KB | 52KB(65%削減) |
| タイムライン表示速度 | 1.8秒 | 0.6秒 |
APIコール数3.2回→1回、データ転送量65%削減、表示速度3倍。1エンドポイントで必要なデータだけを取得する——それだけでこの差がつく。
状況: 従業員60名のプロジェクト管理SaaS。Web・iOS・Android向けにそれぞれ専用のREST APIエンドポイントを作っており、APIエンドポイント数が142個に膨れ上がっていた。
問題: 新機能追加のたびに3プラットフォーム分のAPIを作成・テスト。バックエンドチーム4名がAPI作成だけで工数の40%を消費。
GraphQL化:
- 共通スキーマ1つで3プラットフォームに対応
- Webは詳細データ(ガントチャート情報含む)を取得
- モバイルはサマリーデータのみ取得(同じエンドポイント)
- クライアントが必要なフィールドを選ぶので、専用エンドポイント不要
| 指標 | Before | After |
|---|---|---|
| APIエンドポイント数 | 142 | 1(/graphql) |
| 新機能のAPI開発工数 | 平均5人日 | 平均1.5人日 |
| バックエンドのAPI作成比率 | 40% | 12% |
APIエンドポイント142→1、API開発工数5人日→1.5人日。「クライアントが必要なデータを選ぶ」仕組みにより、プラットフォーム別APIという概念自体が消えた。
状況: 地方自治体が運営する観光情報プラットフォーム。宿泊施設・飲食店・観光スポットなど1,200件のデータをREST APIで提供していたが、データ利用パートナー(旅行サイト、地図アプリ等)ごとに欲しいデータが異なり、カスタムエンドポイントの要望が絶えなかった。
GraphQL化の効果:
- 旅行サイトA: 宿泊施設の空室情報と料金だけを取得
- 地図アプリB: 緯度経度と営業時間だけを取得
- グルメサイトC: 飲食店のメニューと口コミだけを取得
- パートナーが自分で必要なフィールドを選べるので、カスタムAPI開発がゼロに
| 指標 | Before | After |
|---|---|---|
| パートナー対応の開発工数 | 月40時間 | 月2時間(ドキュメント更新のみ) |
| 新規パートナー接続までの期間 | 平均3週間 | 平均2日 |
| APIパートナー数 | 5社 | 18社(1年で3.6倍) |
パートナー対応の開発工数、月40時間→2時間。APIパートナー数は1年で5社→18社に。カスタムAPI開発から解放されたことで、むしろデータ利用者が増えた。
やりがちな失敗パターン#
- N+1問題を放置する — リレーションを辿るたびにDBクエリが発生し、パフォーマンスが壊滅的になる。DataLoaderでバッチ取得を必ず実装すること
- スキーマをDB構造そのままにする — テーブル定義をそのままGraphQLスキーマにすると、クライアントに不要な内部構造が露出する。ユースケースに基づいたスキーマ設計をすること
- クエリの複雑さを制限しない —
{ user { friends { friends { friends { ... } } } } }のような再帰的クエリでサーバーが落ちる。深さ制限とコスト分析を必ず導入すること - RESTを全部置き換えようとする — ファイルアップロードやWebhookなどGraphQLに不向きな処理まで無理にGraphQL化して複雑になる。ユースケースに合わせてRESTとGraphQLを使い分けること
まとめ#
GraphQLは 「クライアントが必要なデータを宣言的に取得する」 強力なAPI設計手法。スキーマファーストで設計し、N+1問題への対策とセキュリティを確保すれば、RESTよりも柔軟で効率的なAPI基盤が作れる。ただし、すべてのAPIをGraphQLにする必要はなく、ユースケースに合わせて使い分けよう。