GraphQLとREST APIの違い|利点と欠点の比較表と使い分けをPythonで確かめる

GraphQLとREST APIの違いは、REST APIが「URL(リソース)ごとに決まった形のデータを返す」のに対し、GraphQLは「1つのエンドポイントに、欲しいフィールドをクエリで指定して取りに行く」点です。GraphQLは取りすぎ・取り足りなさを防げる代わりに、HTTPキャッシュが効きにくく、重いクエリやN+1問題への対策が必要になります。

この記事では、両者の利点と欠点を比較表で整理し、Pythonで実際に動かして「リクエスト回数」「返ってくるデータ量」「DBへの問い合わせ回数」を確かめます。仕様の説明は、GraphQL Foundationの公式ドキュメントとHTTPの標準(RFC 9110)にもとづいています(2026年10月15日確認)。

結論:利点と欠点の比較表

観点REST APIGraphQL
エンドポイントリソースごとに複数(/users/1、/users/1/posts など)基本は1つ(例:/graphql)
取得するデータの形サーバーが決める。不要な項目も返る(オーバーフェッチ)クライアントがクエリで指定した項目だけ返る
関連データの取得画面によっては複数回のリクエストが必要(アンダーフェッチ)関連するデータを1回のクエリでまとめて取得できる
HTTPメソッドGET・POST・PUT・PATCH・DELETEなどで操作を表すPOSTは必須。読み取り(query)はGETにも対応できる
キャッシュURL単位でHTTPキャッシュやCDNを使いやすいPOST中心だとHTTPキャッシュが効きにくい。クライアント側のキャッシュやGET・永続化ドキュメントで補う
エラーの伝え方HTTPステータスコード(404、500など)レスポンス本文の errors。一部成功でも2xxで返ることがある
型と仕様書OpenAPIなどを別途用意することが多いスキーマが型を定義し、イントロスペクションで仕様を問い合わせられる
変更への対応/v1、/v2 のようなバージョン分けが一般的フィールドを追加し、古いものは非推奨にして進化させる設計
サーバー側の負荷対策エンドポイントごとに想定しやすい深いネストや重いクエリへの制限、N+1対策が必要
学習コストHTTPの知識で始められるスキーマ、リゾルバー、クライアントライブラリの理解が必要

どちらが上ということではなく、「誰がデータの形を決めるか」の違いです。RESTはサーバーが決め、GraphQLはクライアントが決めます。この違いから、利点と欠点がそのまま裏表で出てきます。

REST APIとは

RESTは、Roy Fieldingが2000年の博士論文「Representational State Transfer (REST)」で示したアーキテクチャスタイルです。URLでリソースを表し、HTTPのメソッドで操作を表します。たとえば GET /users/1 でユーザーを取得し、DELETE /users/1 で削除します。

HTTPの標準であるRFC 9110でGETは「安全(safe)」なメソッドとされ、応答をキャッシュできる仕組みが定められています。RESTはこのHTTPの仕組みをそのまま使えるのが強みです。RESTの6つの制約やステータスコードの使い分けはRESTful APIとは?RESTの6つの制約と設計の基本で詳しく解説しています。

GraphQLとは

GraphQLは、APIのためのクエリ言語と、その実行方法を定めた仕様です。公式FAQによると、仕様はもともとFacebook(現Meta)で開発され、現在はGraphQL Foundationが管理しています。仕様書の一覧では、最初の公開版が2015年7月、最新のリリースが2025年9月版です(2026年10月15日確認)。

サーバーは「どんな型とフィールドがあるか」をスキーマで定義し、クライアントは次のように欲しい項目だけを書いて送ります。

{ user(id: "1") { name posts { title } } }

返ってくるJSONは、クエリと同じ形になります。これがGraphQLのいちばんの特徴です。

Pythonで確かめる:リクエスト回数とデータ量

同じ画面(ユーザー名と投稿タイトルの一覧)を表示するのに、REST風のAPIとGraphQLで何が違うかを比べます。GraphQLの実行には、公式リファレンス実装のPython版であるgraphql-core(3.3.0、Python 3.12)を使いました。

pip install graphql-core
import json
from graphql import build_schema, graphql_sync

# サンプルデータ(DBの代わり)
USERS = {
    "1": {"id": "1", "name": "佐藤", "email": "sato@example.com",
          "address": "東京都千代田区", "created_at": "2026-04-01"},
}
POSTS = [
    {"id": "10", "user_id": "1", "title": "はじめての投稿", "body": "本文A" * 50},
    {"id": "11", "user_id": "1", "title": "二つめの投稿", "body": "本文B" * 50},
]

# --- REST風:エンドポイントごとに決まった形を返す ---
def rest_get(path):
    if path == "/users/1":
        return USERS["1"]
    if path == "/users/1/posts":
        return [p for p in POSTS if p["user_id"] == "1"]

rest = [rest_get("/users/1"), rest_get("/users/1/posts")]
rest_bytes = sum(len(json.dumps(r, ensure_ascii=False).encode()) for r in rest)
print("REST: リクエスト2回,", rest_bytes, "バイト")

# --- GraphQL:必要なフィールドだけを1回で指定する ---
schema = build_schema("""
type User { id: ID! name: String! email: String! address: String posts: [Post!]! }
type Post { id: ID! title: String! body: String! }
type Query { user(id: ID!): User }
""")
def resolve_user(info, id):
    u = dict(USERS[id])
    def posts(info):
        return [p for p in POSTS if p["user_id"] == id]
    u["posts"] = posts
    return u

query = "{ user(id: \"1\") { name posts { title } } }"
result = graphql_sync(schema, query, root_value={"user": resolve_user})
body = json.dumps({"data": result.data}, ensure_ascii=False)
print("GraphQL: リクエスト1回,", len(body.encode()), "バイト")
print(body)

# 存在しないフィールドを要求すると、実行前の検証でエラーになる
bad = graphql_sync(schema, "{ user(id: \"1\") { name phone } }",
                   root_value={"user": resolve_user})
print(bad.data, bad.errors[0].message)

実行結果です。

REST: リクエスト2回, 971 バイト
GraphQL: リクエスト1回, 118 バイト
{"data": {"user": {"name": "佐藤", "posts": [{"title": "はじめての投稿"}, {"title": "二つめの投稿"}]}}}
None Cannot query field 'phone' on type 'User'.

REST風のAPIでは、ユーザーと投稿で2回のリクエストが必要で、画面に使わないメールアドレス、住所、本文まで返ってきました(オーバーフェッチ)。GraphQLでは1回のリクエストで、指定した name と title だけが返っています。また、スキーマにない phone を要求すると、実行する前の検証の段階でエラーになりました。型が決まっていることの利点です。

GraphQLの欠点をコードで確かめる:N+1問題

GraphQLはクライアントが自由にネストしたクエリを書けるため、サーバー側の作り方によってはDBへの問い合わせが一気に増えます。ユーザー5人とそれぞれの投稿を取得してみます。

from graphql import build_schema, graphql_sync

USERS = [{"id": str(i), "name": f"user{i}"} for i in range(1, 6)]
POSTS = [{"user_id": str(i % 5 + 1), "title": f"post{i}"} for i in range(20)]
db_queries = []  # 実行された「SQL」を記録する

def fetch_posts_for(user_id):
    db_queries.append(f"SELECT * FROM posts WHERE user_id = {user_id}")
    return [p for p in POSTS if p["user_id"] == user_id]

def users_resolver(info):
    db_queries.append("SELECT * FROM users")
    return [dict(u, posts=lambda info, uid=u["id"]: fetch_posts_for(uid))
            for u in USERS]

schema = build_schema("""
type Post { title: String! }
type User { name: String! posts: [Post!]! }
type Query { users: [User!]! }
""")
r = graphql_sync(schema, "{ users { name posts { title } } }",
                 root_value={"users": users_resolver})
print("ユーザー数:", len(r.data["users"]))
print("DBへの問い合わせ回数:", len(db_queries))
for q in db_queries[:3]:
    print(" ", q)
ユーザー数: 5
DBへの問い合わせ回数: 6
  SELECT * FROM users
  SELECT * FROM posts WHERE user_id = 1
  SELECT * FROM posts WHERE user_id = 2

ユーザー一覧に1回、ユーザーごとの投稿に5回、合計6回の問い合わせが発生しました。ユーザーがN人なら1+N回になるので「N+1問題」と呼ばれます。公式FAQでも、GraphQLのサービスがDBに何度も問い合わせる問題は、バッチ処理やDataLoaderのような仕組みで解決するのが一般的だと説明されています。

RESTでも同じ問題は起きますが、エンドポイントごとに返すデータが決まっているので、サーバー側で最適なSQLを用意しやすいという違いがあります。

キャッシュの違い

RESTでは、URLそのものがデータの識別子になるため、ブラウザやCDNのHTTPキャッシュをそのまま使えます。GraphQLの公式ドキュメント「Caching」も、エンドポイント型のAPIではURLがグローバルに一意な識別子になるのに対し、GraphQLにはURLに相当するものがないため、オブジェクトに一意なIDを持たせることを勧めています。

ただし「GraphQLはHTTPキャッシュが使えない」わけではありません。「Serving over HTTP」では、サーバーはPOSTに対応しなければならず、読み取りのqueryについてはGETにも対応してよいとされ、GETを使えばHTTPキャッシュやCDNのキャッシュを活用しやすいと書かれています。クエリが長くなってURLの長さ制限に当たる場合は、クエリ本文を事前にサーバーに登録してIDだけを送る「persisted documents(永続化ドキュメント)」などの方法があります。

エラーとステータスコードの違い

RESTでは「見つからない」は404、「サーバーの不具合」は500のように、HTTPステータスコードで結果を伝えます。GraphQLでは、レスポンス本文の errors にエラーが入ります。「Serving over HTTP」によると、レスポンスに data があり null でなければ、errors を含んでいても2xxのステータスで返すとされています。HTTPには「一部だけ成功」を表すステータスコードがないためです。監視ツールでステータスコードだけを見ていると、GraphQLのエラーを見落とすことがあるので注意が必要です。

セキュリティ:GraphQLで追加で考えること

認証・認可、HTTPS、入力値の検証は、どちらの方式でも同じように必要です。GraphQLではそれに加えて、クライアントが重いクエリを自由に書けることへの対策がいります。公式ドキュメント「Security」では、次のような方法が挙げられています。

  • 深さの制限:ネストの深さに上限を設け、超えるクエリは実行前にエラーにする
  • クエリの複雑さの分析:型やフィールドに重みをつけてコストを見積もり、上限を超えたら拒否する
  • レート制限:見積もったコストをクライアントごとの予算から差し引く
  • trusted documents:自社のアプリだけが使うAPIなら、事前に登録したクエリ以外は実行しない

どちらを選ぶか:使い分けの目安

状況向いている方式理由
CRUD中心のシンプルなAPI、社外に公開するAPIRESTHTTPの知識で使え、キャッシュやステータスコードの仕組みをそのまま使える
CDNで大量の読み取りをさばきたいREST(またはGraphQLのGET+永続化ドキュメント)URL単位のキャッシュが効く
画面ごとに必要なデータの組み合わせが違う(Webとスマホアプリなど)GraphQLクライアントが必要な項目だけを1回で取れる
複数のサービスのデータを1つの窓口にまとめたいGraphQLスキーマで1つのAPIとして見せられる
ファイルのアップロード・ダウンロードが中心RESTHTTPの仕組みで素直に扱える
チームにGraphQLの経験者がいないまずRESTN+1対策やクエリ制限まで設計できる体制が必要になる

実際のサービスでは、外部公開はREST、自社のフロントエンド向けはGraphQLのように併用する例もあります。API全体の基礎(HTTPの仕組み、認証方式など)から整理したい場合はAPIとは?Web通信の仕組みからREST・GraphQL・認証までもあわせてご覧ください。

よくある質問

Q. GraphQLとREST APIの一番の違いは何ですか?

データの形を誰が決めるかです。REST APIはURLごとにサーバーが決めた形でデータを返し、GraphQLは1つのエンドポイントでクライアントがクエリに書いた項目だけを返します。

Q. GraphQLのデメリットは何ですか?

POST中心だとHTTPキャッシュが効きにくいこと、自由なクエリで重い処理やN+1問題が起きやすいこと、深さ制限やコスト制限などの対策が必要なこと、エラーが2xxのレスポンスに含まれて返ることがあることです。

Q. REST APIのメリットは何ですか?

HTTPのメソッドとステータスコードをそのまま使うので理解しやすく、URL単位でブラウザやCDNのキャッシュを使えることです。ツールや情報も多く、社外に公開するAPIで扱いやすい方式です。

Q. GraphQLはRESTの後継ですか?

後継ではありません。データの取り方の設計が違う別の方式で、用途によって向き不向きがあります。同じサービスの中で両方を使い分けることもあります。

まとめ

REST APIはURLとHTTPの仕組みを素直に使う方式で、シンプルさとキャッシュのしやすさが利点です。GraphQLはクライアントが必要なデータを1回で指定できる方式で、画面ごとに必要なデータが違うアプリで力を発揮しますが、キャッシュ、N+1問題、重いクエリへの対策を自分で設計する必要があります。迷ったら、まず扱うデータとクライアントの種類を書き出し、「データの形を決めるのはサーバーとクライアントのどちらがよいか」から考えると選びやすくなります。

あわせて読みたい

学習の進め方はPython学習のロードマップ、職種ごとの違いはエンジニアの職種の違いにまとめています。

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら