この用語をシェア
GraphQLとは
GraphQLは、Facebook(現Meta)が社内のモバイルアプリ向けデータ取得基盤として2012年頃から開発し、2015年に仕様とリファレンス実装をオープンソース化したAPIのためのクエリ言語およびサーバーサイドランタイムです。2018年には特定企業の管理下から離れ、Linux Foundation傘下の「GraphQL Foundation」に移管され、以後は中立的な仕様策定団体によって仕様書(GraphQL Specification)の更新が続けられています。直近の正式な仕様改訂は2021年10月版で、その後も@oneOf入力型や増分配信ディレクティブなど細かな拡張がワーキンググループで議論され続けています。
「クエリ言語」と「ランタイム」という2つの側面を持つ点がGraphQLの理解を難しくしがちなポイントです。クエリ言語としてのGraphQLは、クライアントが「どのデータを、どの形で返してほしいか」を宣言的に記述するための構文を提供します。一方でランタイムとしてのGraphQLは、サーバー側でそのクエリを受け取り、スキーマと照合して検証し、各フィールドに対応する処理(リゾルバ)を呼び出してレスポンスを組み立てる実行エンジンを指します。この2つが組み合わさることで、クライアントは「エンドポイントの数だけ叩き分ける」のではなく「1つの窓口に必要な形を注文する」感覚でAPIを利用できます。
実際の採用例としては、GitHubが2016年に公開した「GitHub API v4」がGraphQLベースであることや、ShopifyのAdmin APIがGraphQL版を主軸に据えていること、GitLabがREST APIと並行してGraphQL APIを提供していることなどが広く知られています。モバイルアプリのように通信量や往復回数(レイテンシ)を抑えたいクライアント、あるいは複数の下流サービスを1つの窓口に集約したいBFF(Backend for Frontend)層で採用されることが多い技術です。
GraphQLの仕組み
GraphQLの中核には「スキーマ」「型システム」「リゾルバ」という3つの要素があります。まずサーバー開発者は、APIが公開するデータ構造をSDL(Schema Definition Language)という専用の記法でスキーマとして定義します。スキーマにはScalar型(Int・Float・String・Boolean・ID)、任意のオブジェクト型、列挙型(Enum)、複数の型に共通するふるまいを表すインターフェース型、複数の異なる型のいずれかを返すUnion型、そしてミューテーションの引数専用のInput型などが登録されます。型には!で必須(Non-Null)を、[ ]でリストを表現でき、これによりクライアント側は型定義を見るだけでレスポンスの形をほぼ正確に予測できます。
スキーマ上の各フィールドには「リゾルバ」と呼ばれる関数が1つずつ対応づけられており、そのフィールドの値をどこから(DB、別のマイクロサービス、外部API、キャッシュなど)取得するかを実装します。クライアントから送られたクエリは、サーバー側で次のような流れで処理されます。①構文解析(パース)、②スキーマに対する検証(存在しないフィールドや型不一致がないかのチェック)、③実行計画の構築、④ルートフィールドから子フィールドへ向けてリゾルバをツリー状に呼び出す実行、⑤結果をクエリの形にそのままマッピングしてJSONへ組み立てる、という順序です。ネストしたフィールドのリゾルバは並行して呼び出されることが多く、これが後述するN+1問題の温床にもなります。
通信方式としては、通常HTTPの単一エンドポイント(例:/graphql)に対してPOSTリクエストを送り、リクエストボディのJSONにquery(クエリ文字列)・variables(変数)・operationNameを含めるのが一般的です。REST APIのようにURLパスやHTTPメソッドでリソースを区別するのではなく、ボディの中身がAPIの意味を決める点が大きな違いです。また、GraphQLはスキーマそのものをクエリで取得できる「イントロスペクション」という仕組みを標準で備えており、これによりGraphiQLやGraphQL Playground、Apollo Studioのようなツールが、ドキュメントを手書きしなくても対話的なAPI探索環境を自動生成できます。
GraphQLには読み取り専用の「クエリ」に加えて、データを変更する「ミューテーション」、そしてサーバーからクライアントへイベントを push する「サブスクリプション」という3種類の操作(Operation Type)があります。サブスクリプションは多くの実装でWebSocket(graphql-wsプロトコルなど)上に構築され、リアルタイムなデータ更新の配信に使われます。
具体的なリクエスト・レスポンス例
まずはもっとも基本的な、単一クエリの例です。クライアントは必要なフィールドだけを指定し、サーバーはその形のままJSONを返します。
// クエリ:ユーザー情報とその投稿一覧を1回のリクエストで取得
query {
user(id: "123") {
name
email
posts {
title
content
createdAt
}
}
}
// レスポンス(要求したフィールドだけが返る)
{
"data": {
"user": {
"name": "田中太郎",
"email": "tanaka@example.com",
"posts": [
{
"title": "GraphQLの基本",
"content": "GraphQLは...",
"createdAt": "2024-01-01T00:00:00Z"
}
]
}
}
}
実務では固定文字列をそのまま埋め込むのではなく、変数(Variables)を使ってパラメータ化するのが定石です。これによりクエリ文字列自体はキャッシュ・永続化しやすい固定形になり、値だけを差し替えられます。
// 変数を使ったクエリ定義
query GetUser($userId: ID!) {
user(id: $userId) {
name
email
}
}
// リクエストボディ全体(実際にPOSTされるJSON)
{
"query": "query GetUser($userId: ID!) { user(id: $userId) { name email } }",
"variables": { "userId": "123" }
}
データを変更する操作は「ミューテーション」として定義します。慣習として、ミューテーションは入力値をひとつのInput型(input)にまとめ、戻り値も更新後のオブジェクトとエラー情報などをまとめたペイロード型で返すパターンがよく使われます。
// ミューテーション:投稿の新規作成
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
post {
id
title
createdAt
}
errors {
field
message
}
}
}
// variables
{
"input": { "title": "GraphQL入門", "content": "GraphQLの基礎をまとめます" }
}
サブスクリプションは、クライアントが購読を開始すると、対象のイベントが発生するたびにサーバーからデータが配信される操作です。チャットの新着メッセージ通知や、在庫数のリアルタイム更新などに使われます。
// サブスクリプション:新着コメントの購読
subscription OnCommentAdded($postId: ID!) {
commentAdded(postId: $postId) {
id
author
text
createdAt
}
}
複数のクエリで同じフィールド群を繰り返し記述したくない場合は「フラグメント(Fragment)」を使って共通部分を切り出せます。フラグメントの活用はクエリの保守性を高める基本テクニックとして広く使われています。
fragment UserBasicFields on User {
id
name
email
}
query {
user(id: "123") {
...UserBasicFields
posts { title }
}
}
GraphQLのメリット
- Over-fetching / Under-fetchingの解消:REST APIでは1つのエンドポイントが返すフィールドが固定されがちで、「使わないデータまで大量に返ってくる(Over-fetching)」「必要なデータが1回では揃わず複数回リクエストが必要(Under-fetching)」という問題が起きやすい一方、GraphQLではクライアントが要求したフィールドだけが返るため、モバイル回線のような帯域が限られる環境でも通信量を抑えやすくなります。
- 1リクエストでの複数リソース集約:関連する複数のリソース(ユーザーとその投稿とコメント数など)を、REST であれば複数エンドポイントへ往復するところを、GraphQLでは1回のリクエストにまとめられます。特にネットワーク往復(ラウンドトリップ)の遅延が積み重なりやすいモバイルや低速回線での体感速度改善に寄与します。
- 強力な型システムと開発時の安全性:スキーマがそのまま「契約(コントラクト)」として機能するため、
graphql-code-generatorのようなツールでTypeScriptの型定義を自動生成し、フロントエンドとバックエンドの型不一致をビルド時に検出できます。 - 自己文書化とイントロスペクション:スキーマ自体がAPI仕様書を兼ねるため、別途ドキュメントを書いて同期させる負担が減ります。GraphiQLなどのツールはイントロスペクションを通じて型定義から自動的に補完・検索可能なドキュメントを生成します。
- バージョンレスな進化:REST APIでよくある
/v1//v2/のようなURLバージョニングに頼らず、新しいフィールドを追加し、古いフィールドは@deprecatedディレクティブで非推奨扱いにしてから段階的に廃止する、という進化の仕方が一般的です。 - マイクロサービスの集約(BFF/Federation):複数のバックエンドサービスを1つのGraphQLスキーマにまとめて公開するBFFパターンや、後述するGraphQL Federationにより、フロントエンドは背後のサービス分割を意識せずにデータを取得できます。
GraphQLの課題・デメリット
- N+1問題:一覧データの各要素に対して関連データを取得するリゾルバが個別に呼ばれると、要素数がNのとき本来1回で済むはずのクエリがN+1回発行されてしまう問題です。例えば「投稿一覧を取得し、各投稿の著者情報も取得する」クエリでは、投稿の取得で1回、各投稿の著者取得でN回のデータベースアクセスが発生し得ます。この対策として、同一リクエスト内で発生した個々の取得要求をまとめてバッチ化するDataLoaderパターンがデファクトスタンダードとして広く使われています。
- キャッシュ設計の複雑さ:REST APIはURLとHTTPメソッドの組み合わせでリソースを一意に識別できるため、CDNやブラウザのHTTPキャッシュ(
ETag、Cache-Controlなど)をそのまま活用できます。一方GraphQLは単一エンドポイントへのPOSTが基本のため、URLベースのキャッシュがそのままでは効きません。実務では、正規化されたクライアントサイドキャッシュを持つApollo ClientやRelay、またはクエリをサーバーに事前登録してハッシュIDでGET可能にする「Persisted Queries」といった手法でこれを補います。 - 学習コストと設計の難しさ:スキーマ設計・型システム・リゾルバの実行モデルなど、REST APIにはない概念の習得が必要です。特にスキーマ設計は一度公開すると変更コストが高くなるため、初期段階での設計品質が長期的な保守性を大きく左右します。
- 任意クエリによる過負荷リスク:クライアントが自由にクエリ構造を組み立てられるという利点は裏を返せば、深くネストしたクエリや同じフィールドを大量に繰り返すクエリによってサーバーに過大な負荷をかけられるリスクでもあります(後述のセキュリティ対策で詳述)。
- ファイルアップロードが標準仕様に含まれない:GraphQL自体の仕様にはファイルアップロードの定義がなく、コミュニティ仕様である
graphql-multipart-request-spec(multipart/form-dataとの組み合わせ)や、署名付きURLを発行してアップロード自体は別チャネルで行う設計が一般的な回避策として使われています。
REST APIとの違い
REST APIとGraphQLは対立概念というより、それぞれ得意な状況が異なる設計思想です。REST APIはリソース単位でURLとHTTPメソッド(GET/POST/PUT/DELETE)を対応させるシンプルな構造を持ち、HTTPキャッシュ・CDN・ステータスコードによるエラー表現など、Web標準のインフラをそのまま活かせる点が強みです。一方GraphQLは、クライアント主導でレスポンス形状を決められる柔軟性と、複数リソースの集約を1リクエストで行える効率性に強みがあります。
| 項目 | REST API | GraphQL |
|---|---|---|
| エンドポイント | リソースごとに複数(例:/users, /posts) | 基本的に単一(例:/graphql) |
| データ取得の粒度 | エンドポイントごとに固定(Over/Under-fetchingが起きやすい) | クライアントがフィールド単位で指定可能 |
| HTTPメソッド | GET/POST/PUT/PATCH/DELETEを使い分け | 基本はPOST(クエリはGETも可能) |
| キャッシュ | URL単位のHTTP/CDNキャッシュが容易 | クライアント側の正規化キャッシュやPersisted Queriesが必要 |
| バージョニング | /v1/、/v2/のようなURLバージョニングが一般的 | フィールド追加+@deprecatedによる段階的廃止が一般的 |
| エラー表現 | HTTPステータスコード(404、500等) | 基本はHTTP 200+レスポンス内のerrorsフィールド |
| 学習コスト | 比較的低い | スキーマ設計・型システムの理解が必要でやや高い |
| 向いている場面 | シンプルなCRUD、公開APIでのキャッシュ重視 | 複雑な画面構成、モバイル、複数サービスの集約(BFF) |
実務判断としては、レスポンス形状が固定でよく、キャッシュや既存のHTTPインフラの恩恵を優先したい場合はREST APIが適しています。逆に、フロントエンドの要求データが画面ごとに大きく異なる、複数のバックエンドサービスをまたいだデータを1画面にまとめたい、といった場合はGraphQLのBFF的な使い方が有効です。両者は排他的ではなく、既存のREST APIの手前にGraphQLゲートウェイを1枚被せて集約する構成も広く行われています。
実務での設計ポイントとセキュリティ対策
設計のベストプラクティス
- スキーマファースト設計:実装より先にSDLでスキーマを設計し、フロントエンド・バックエンド双方でレビューしてから実装に入ると、後からの破壊的変更を避けやすくなります。
- Relayスタイルのページネーション:一覧データには
edges/node/pageInfo(hasNextPage、endCursorなど)で構成される「コネクション」パターンを使うことで、カーソルベースの一貫したページネーションをスキーマ全体で統一できます。 - ミューテーションの入出力パターン:ミューテーションの引数は単一のInput型にまとめ、戻り値も更新結果とエラー情報をまとめたペイロード型にする設計が定石とされています。
- 非推奨は削除ではなく@deprecated:フィールドを廃止したいときは即座に削除せず、まず
@deprecated(reason: "...")を付与して利用状況をモニタリングし、影響範囲を確認してから段階的に取り除きます。 - N+1対策としてのDataLoader導入を前提にする:関連データを持つフィールドのリゾルバは、設計段階からDataLoaderなどのバッチ・キャッシュ機構を通す前提で実装し、後付けの最適化にしないことが重要です。
セキュリティ上の注意点
- クエリ深度制限(Depth Limiting):フィールドが自己参照的にネストできるスキーマ(例:投稿→著者→投稿→著者…)では、悪意あるクライアントが深いネストのクエリを送って処理負荷を指数的に増大させる攻撃が可能です。
graphql-depth-limitのようなミドルウェアで、許容するネスト階層に上限を設けるのが基本対策です。 - クエリ複雑度(コスト)計算:フィールドごとに「コスト」を定義し、1リクエストあたりの合計コストに上限を設けることで、深さだけでなく横方向に広いクエリ(大量のフィールドやリストの多重要求)による負荷も抑えられます。
- フィールドレベルの認可:GraphQLはエンドポイント単位ではなくフィールド単位でデータにアクセスするため、認可(Authorization)もリゾルバ単位で行う必要があります。「このクエリ自体は許可するが、特定のフィールドだけは権限がない利用者には返さない」といった制御をリゾルバ内、もしくはディレクティブ(例:
@authのようなカスタムディレクティブ)で実装するのが一般的です。 - 本番環境でのイントロスペクション制限:イントロスペクションは開発体験の向上に有用ですが、内部のスキーマ構造を第三者に把握されるリスクもあるため、公開APIでない社内向けAPIなどでは本番環境で無効化するかアクセス制限を設ける判断が取られることがあります。
- Persisted Queries(永続化クエリ):クライアントが送信できるクエリをあらかじめ登録済みのものに限定し、任意のクエリ文字列を受け付けないことで、攻撃面を狭めつつCDNキャッシュとの相性も改善できます。
- レート制限もコストベースで:単純なリクエスト数ベースのレート制限では、1リクエストの内容次第で負荷が大きく変わるGraphQLの特性に対応しきれないため、前述のクエリコストを単位としたレート制限を組み合わせる設計が実務では定石とされています。
主要なツール・実装
- Apollo Server / Apollo Client:サーバー実装とクライアントの正規化キャッシュ、双方を提供するフルスタックのGraphQLプラットフォームで、Apollo Federationによるマイクロサービス統合機能も含みます。
- Relay:Meta製のReact向けGraphQLクライアントで、コネクションベースのページネーションやフラグメント合成を前提にした設計が特徴です。
- GraphQL Yoga:軽量でエッジ環境にも展開しやすいことを意識したGraphQLサーバー実装です。
- GraphiQL / GraphQL Playground / Apollo Studio:スキーマのイントロスペクションを利用した対話的なクエリ実行・検証環境です。
- Hasura:既存のデータベーススキーマからGraphQL APIを自動生成するツールで、リゾルバを手書きせずにCRUD操作を公開できます。
- graphql-code-generator:スキーマからTypeScriptの型定義やReact Hooksを自動生成し、フロントエンドの型安全性を高めるツールです。
- DataLoader:Meta製のバッチ処理・キャッシュライブラリで、N+1問題対策の実装リファレンスとして広く参照されています。
2025〜2026年の最新動向
マイクロサービス化が進む組織では、複数チームがそれぞれ管理する部分スキーマを1つの統合スキーマとして公開する「GraphQL Federation」(Apollo Federationがその代表的な実装)の活用が定着してきています。ゲートウェイが各サブグラフに問い合わせを振り分け、クライアントからは単一の統合スキーマとして見える構成により、大規模組織でもチームごとの独立したデプロイと、フロントエンドから見た一貫したAPI体験を両立させやすくなっています。
仕様面では、レスポンスの一部を後回しで配信できる@defer・@streamディレクティブが、GraphQL仕様のワーキンググループで継続的に議論・実験されており、Apollo ServerやRelayなど一部の実装が先行して対応を進めています。これにより、表示に時間のかかる重いフィールドを後から追いで取得しつつ、軽いフィールドから先に画面へ反映する、といった段階的なレンダリングが行いやすくなります。また、GraphQL Over HTTPの仕様化やPersisted Queriesの標準化に向けた議論も継続しており、キャッシュ・CDN連携の弱点を仕様レベルで補う方向性が続いています。
実装面では、Cloudflare WorkersのようなエッジランタイムでGraphQLサーバーを動かす構成や、GoのgqlgenやKotlin/Java系実装など、多言語でのスキーマファースト実装が充実してきました。さらに、LLMを使ったエージェント型アプリケーションが、既存のGraphQLスキーマをツール定義として読み込み、必要なデータをその場でクエリ生成して取得する、といった連携も実務で試みられ始めています。GraphQLの「スキーマが仕様書を兼ねる」という性質は、人間の開発者だけでなくAIエージェントにとってもAPIの理解を容易にする方向に働くと見られています。
よくある質問(FAQ)
Q. GraphQLとは?
A. GraphQLはFacebook(Meta)が開発したAPI用クエリ言語およびランタイムです。クライアントが必要なデータを正確に指定して取得でき、1回のリクエストで複数リソースのデータを取得できます。Over-fetchingとUnder-fetchingの問題を解決するために設計されました。
Q. GraphQLのデメリットは?
A. キャッシュ設計が複雑になりやすい、N+1クエリ問題が起きやすい、学習コストが高い、ファイルアップロードが標準仕様に含まれない、クエリの深さや複雑度に対するセキュリティ対策が別途必要、といった点がデメリットとして挙げられます。
Q. GraphQLとRESTの使い分けは?
A. フロントエンドが画面ごとに異なる柔軟なデータ取得を必要とする場合や、複数サービスをまたいだデータ集約が必要な場合はGraphQLが向いています。逆にシンプルなCRUD操作や、HTTP/CDNキャッシュの恩恵を優先したい公開APIではRESTが適しています。BFF(Backend for Frontend)パターンで、内部はREST、フロントエンド向けの窓口だけGraphQLにするハイブリッド構成も一般的です。
Q. N+1問題とDataLoaderは具体的に何をしているのですか?
A. 一覧の各要素ごとに関連データを個別取得すると、要素数分のクエリが追加で発行されてしまうのがN+1問題です。DataLoaderは、同一リクエスト内で発生した複数の取得要求を一定時間内にまとめてバッチ化し、1回のクエリで解決してから各呼び出し元に結果を配る仕組みで、これによりデータベースやAPIへの呼び出し回数を大きく減らせます。
Q. GraphQLで最低限行うべきセキュリティ対策は?
A. クエリの深さ(ネスト階層)とコスト(複雑度)に上限を設けること、フィールド単位での認可チェックを行うこと、本番環境でのイントロスペクション公開範囲を見直すことが基本です。公開APIとして提供する場合は、任意クエリを制限するPersisted Queriesの導入も有効な選択肢です。
Q. 小規模なプロジェクトでもGraphQLを使うべきですか?
A. 画面ごとのデータ要求がシンプルで、エンドポイント数もそれほど多くない小規模プロジェクトでは、学習コストやキャッシュ設計の複雑さに見合うメリットが小さいことが多く、REST APIで十分なケースが少なくありません。フロントエンドの要求が今後複雑化する見込みがある、複数クライアント(Web/モバイル)で異なるデータ形状が必要、といった見通しがある場合にGraphQL導入の検討価値が高まります。
Q. ファイルアップロードはどう実装しますか?
A. GraphQL仕様自体にはファイルアップロードの定義がないため、コミュニティ仕様であるgraphql-multipart-request-specを実装したサーバー・クライアントの組み合わせを使うか、アップロード用の署名付きURLをミューテーションで発行し、実際のファイル転送はGraphQLの外側(別のHTTPエンドポイントやオブジェクトストレージへの直接アップロード)で行う設計がよく採られます。
