プログラミング ソフトウェア 公開日 2026.04.04 更新日 2026.09.21

GraphQL とは何か?REST API との違いと使い分けの判断軸

同じ記事一覧をRESTGraphQLで取得する要求・応答を比較します。GraphQL.jsで確かめた取得関数の呼出回数と部分エラーの例から、柔軟な取得の利点、サーバー側の仕事、採用の判断軸を説明します。

先に要点

  • GraphQLは、サーバーが公開した型と項目から、クライアントが必要なデータを選ぶ仕組みです。DBを直接操作する言語ではありません。
  • REST APIでも必要なデータをまとめて返せます。違いは「通信が必ず1回か」ではなく、返す形を誰がどの範囲で決めるかです。
  • 同じ記事一覧を例に要求と応答を比較し、GraphQLの取得処理を変えると呼出回数がどう変わるかを実行して確かめます。

記事一覧にタイトルと著者名を出したい。スマホ版ではタイトルだけ、管理画面では著者の情報も詳しく欲しい。このように、同じデータから画面ごとに違う項目を使うとき、APIの返し方が設計上の問題になります。

GraphQLREST APIを、この一つの画面を通して比べます。取得の柔軟さだけでなく、その裏で必要になるデータ取得・認可・エラー処理まで見ると、採用する理由と見送る理由を整理できます。

GraphQLRESTは、何を決める仕組みなのか

GraphQLAPIの問い合わせ言語と実行の仕組みです。サーバーは「記事にはタイトルと著者がある」という型を定義し、クライアントはその中から欲しい項目を指定します。この型の定義をスキーマと呼びます。

RESTは、リソースを扱うインターフェースやステートレスな通信などを組み合わせた設計様式です。この記事では、URLで記事や利用者を表し、GETなどのHTTPメソッドで操作する一般的なHTTP APIを比較対象にします。単にURLが複数あることだけがRESTの定義ではありません。

どちらでも、DBから取り出す処理や、他のサービスへ問い合わせる処理はサーバー側で実装します。GraphQLを選んでも、SQLや業務ロジックが不要になるわけではありません。

同じ記事一覧をREST APIで取得する

まず「記事と著者を別々に返すAPI」がある場合を考えます。下のURLと応答は比較のための設計例です。

GET /articles?limit=2
[
  {"id":"1","title":"Article 1","authorId":"u1"},
  {"id":"2","title":"Article 2","authorId":"u2"}
]

これだけでは著者名がないので、クライアントはGET /authors/u1GET /authors/u2で名前を取りに行きます。この設計なら一覧1回と著者2回です。画面の表示に足りないデータを追加取得する問題をアンダーフェッチと呼びます。

ただし、RESTでは必ず分割しなければならないわけではありません。たとえばGET /articles?limit=2&include=authorで著者も含めるAPIを設計できます。

[
  {"title":"Article 1","author":{"name":"Author A"}},
  {"title":"Article 2","author":{"name":"Author B"}}
]

この場合は1回のHTTPリクエストで画面に必要な情報が揃います。includeはこの例で決めたパラメータで、REST共通の機能ではありません。取得項目を選ぶパラメータや、画面向けの集約APIを用意することも可能です。

同じ一覧をGraphQLで取得する

GraphQL側では、まずサーバーが次のようなスキーマを公開します。

type Query {
  articles(first: Int!): [Article!]!
}
type Article {
  id: ID!
  title: String!
  author: Author
}
type Author {
  id: ID!
  name: String!
}

Queryは読み取りの入口です。first: Int!は必須の整数引数、[Article!]!は一覧自体も各要素もnullにしない型を意味します。author: Authorには!がないため、著者はnullになり得ます。この違いは後でエラーを扱うときに効いてきます。

クライアントは、公開された項目からタイトルと著者名を選びます。

query ArticleList($count: Int!) {
  articles(first: $count) {
    title
    author { name }
  }
}

変数に{"count":2}を渡した結果は次の形です。この例はNode.js 22.22.0とGraphQL.js 16.11.0で実行し、応答を確認しています。

{
  "data": {
    "articles": [
      {"title":"Article 1","author":{"name":"Author A"}},
      {"title":"Article 2","author":{"name":"Author B"}}
    ]
  }
}

要求で選んだ形がdata以下に現れ、指定していないIDは返っていません。スマホ版で著者名が不要なら、問い合わせからauthorを外せます。既存スキーマ内の項目を組み合わせる変更なら、画面ごとに新しいURLを増やさずに対応できます。

一方、存在しない項目は自由に取得できません。実験では未定義のnonexistentを要求すると、実行前の検証でエラーとなり、dataは返りませんでした。新しい情報を公開するには、サーバー側のスキーマと取得処理の追加が必要です。

一覧の作り方画面側の要求サーバー側で用意するもの
RESTで個別取得記事を取得し、著者を追加取得記事と著者の各リソース
RESTで集約著者込みの一覧を1回取得集約した表現、またはinclude等の仕様
GraphQL記事と著者名を選択して要求型・関連・項目ごとの取得処理

不要な項目を多く返すことをオーバーフェッチと呼びます。GraphQLは返却項目を選べますが、裏の取得処理が全列を読む実装ならDB側の仕事は減りません。応答の形とデータ取得の効率は分けて見る必要があります。

1回の要求の裏で、21回取得してしまうことがある

GraphQLでは、各フィールドの値を返す関数をリゾルバーと呼びます。記事一覧のリゾルバーが20件を取得し、各記事の著者リゾルバーが1件ずつ取りに行くと、一覧1回+著者20回になります。これがN+1問題の典型的な形です。

今回、20記事と2人の著者をメモリ内に用意し、同じ問い合わせを二つの実装で実行しました。個別取得では各記事から著者の取得関数を呼びます。一括取得ではDataLoader 2.2.3でIDをまとめ、まとめたIDに対応する著者を返します。

const loader = new DataLoader(async ids => {
  calls.authors++;
  return ids.map(id => authors.find(author => author.id === id));
});

上は一括取得部分の抜粋です。実DBに置き換えるなら、ここでID群を一括検索し、要求されたIDの順序に対応させて結果を返します。ループ内でSQLを1本ずつ発行すると、まとめた意味がなくなります。

実行した実装記事の取得関数著者の取得関数返却データ
各記事から個別取得1回20回20記事と著者名
DataLoaderでまとめる1回1回上と完全一致

測ったのは取得関数の呼出回数です。実DBのSQL回数や応答速度のベンチマークではありません。それでも、GraphQLの要求を変えずに、裏の取得処理だけで仕事量が変わることは確認できます。RESTの集約APIでも同じN+1は起きるので、方式名だけで速さは決まりません。

DataLoaderは通常リクエストごとに作り、異なる利用者の結果を共有しないようにします。キャッシュは認可を代行しません。性能を比べるなら同じ画面・件数・権限条件で、HTTP往復、DBアクセス、サーバー処理時間、応答サイズをそれぞれ測ります。

部分的な失敗を、画面でどう扱うか

記事の取得には成功したが、二つ目の著者を返す処理だけが失敗する場合も試しました。応答にはdataerrorsが同時に含まれました。位置情報を省略すると次の形です。

{
  "errors": [{
    "message": "Author unavailable",
    "path": ["articles", 1, "author"]
  }],
  "data": {
    "articles": [
      {"title":"Article 1","author":{"name":"Author A"}},
      {"title":"Article 2","author":null}
    ]
  }
}

配列の位置は0から数えるので、1は二つ目の記事です。タイトルは表示でき、著者名だけ取得できません。画面では「著者情報を取得できません」として記事を残すのか、全体を失敗にするのかを決めます。

この例で一部だけ残せたのは、スキーマのauthorがnullを許すためです。nullを許さないフィールドで失敗すると、nullを許す親まで影響が広がります。型を厳しくすることと、部分表示できる範囲は関係しています。

HTTPで配信する場合も、ステータスだけで成功を判断せず応答本文を扱います。HTTPステータスの具体的な選択はエラーの段階やメディアタイプにも関わるため、「GraphQLはすべて200」と覚えない方がよいです。上の実験は実行エンジンの検証で、HTTPサーバーの実験ではありません。

認可・負荷・キャッシュは、自由に取得できる範囲とセットで設計する

スキーマに項目があることは、誰でも読んでよいという意味ではありません。user(id: ...)のIDを変えるだけで他人の非公開情報を取得できないよう、対象レコードやフィールドの認可を業務ロジック側で行います。RESTとGraphQLを併用するなら、同じ権限判断を共有すると食い違いを防ぎやすくなります。

また、first: Int!は整数であることを表すだけで、件数の上限ではありません。実験では取得処理に1〜20件の範囲チェックを入れました。本番では扱うデータ量に合わせ、件数、関連の深さ、別名指定による広がり、処理コストを制限します。浅い問い合わせでも、大量の一覧を要求されれば重くなります。

公開コンテンツのキャッシュ

RESTのGETはURLごとに扱いやすい設計です。GraphQLも読み取りをGETで送る方法がありますが、問い合わせと変数を含む識別方法を設計します。

利用者ごとに異なるデータ

共有CDNに他人の結果を返させない設計が必要です。ブラウザ内のキャッシュと、複数利用者が使う共有キャッシュを分けて考えます。

何が遅いかを調べるログ

GraphQLではURLだけでなく操作名・処理時間・取得回数を追います。変数には個人情報が入り得るため、要求全文を無条件に記録しません。

採用は、今のAPIで困っている画面から判断する

「外部公開APIだからREST」「管理画面だからGraphQL」と用途名だけでは決まりません。利用者が必要とする形の違いと、その柔軟さを運用する負担を比べます。

現在の困りごと先に試すことGraphQLを検討しやすい条件
一つの一覧だけ取得回数が多い既存RESTに集約や一括取得を追加同様の画面が多く、要求の組み合わせが継続して変わる
Webとモバイルで必要な項目が違う項目選択や画面向けAPIの維持費を確認共有する型があり、クライアント側で選ぶ利点が大きい
複数のサービスの情報を組み合わせる集約層で認可・タイムアウト・失敗時の表示を設計関連をたどる問い合わせが多く、スキーマを継続管理できる
安定した単純なCRUDを提供している既存APIの仕様・監視を整える方式変更で解消する具体的な課題が出てから比較

最初は、困っている画面を一つ選び、必要な項目と現在の要求回数を書き出します。RESTを改良した案とGraphQL案を並べ、実装量、取得効率、エラー表示、保守担当まで比べると、流行ではなく解消できる課題で判断できます。既存RESTを残し、一部にGraphQLを使う構成も可能です。

REST側の仕様を整理したい場合は、OpenAPI / SwaggerでAPI仕様を共有する方法も役立ちます。

GraphQLとREST APIに関するよくある質問

GraphQLならエンドポイントは必ず一つですか?

単一の入口へ問い合わせる構成が一般的ですが、入口の本数そのものが利点の本体ではありません。サービスや権限境界で分かれることもあります。比較では、必要なデータをどう表現し取得するかを見ます。

更新や削除もGraphQLでできますか?

更新操作にはMutationを定義します。ただし、入力検証、認可、トランザクション、重複実行への対策はサーバー実装の仕事です。Mutationと書けば複数の処理が自動で一つのDBトランザクションになるわけではありません。

GraphQLを入れればN+1は解消しますか?

解消しません。実験のように取得処理をまとめる必要があります。DataLoaderを使う場合も、バッチ関数の内部が個別アクセスのままなら、実DBへの負荷は減りません。

まとめ

GraphQLの強みは、公開された型の中でクライアントが欲しい形を選べることです。その柔軟さに価値がある画面なら有力な選択肢になります。RESTでも集約や項目選択は設計できるため、まず同じ画面を両方式でどう作るか比べ、取得処理と運用まで含めて選びます。

参考情報

公式資料は2026年9月21日に確認。実行例の取得元はメモリ内データです。

あとで見返すならここで保存

読み終わったあとに残しておきたい記事は、お気に入りからまとめて辿れます。