GraphQL vs REST API 徹底比較 — どちらを選ぶべき?実践ガイド2026
API設計において、GraphQLとRESTのどちらを選ぶべきか悩んでいませんか?「GraphQLは新しいから良い」「RESTは枯れているから安定」といった表面的な理解では、プロジェクトに最適な選択ができません。この記事では、両者の特徴を実践的に比較し、どのような場合にどちらを選ぶべきかを明確にします。
REST APIとは?基本を理解する
REST(Representational State Transfer)は、リソースベースのアーキテクチャスタイルです。
REST APIの基本原則
// RESTの基本設計
GET /users // ユーザー一覧取得
GET /users/123 // 特定ユーザー取得
POST /users // ユーザー作成
PUT /users/123 // ユーザー更新
DELETE /users/123 // ユーザー削除
GET /users/123/posts // ユーザーの投稿一覧
GET /posts/456/comments // 投稿のコメント一覧
RESTのレスポンス例
// GET /users/123
{
"id": 123,
"name": "田中太郎",
"email": "tanaka@example.com",
"createdAt": "2026-01-15T10:00:00Z",
"profile": {
"bio": "エンジニアです",
"avatar": "https://example.com/avatar.jpg"
}
}
GraphQLとは?RESTとの違い
GraphQLは、クエリ言語とランタイムの組み合わせです。クライアントが必要なデータを正確に指定できます。
GraphQLの基本クエリ
# 必要なフィールドだけ取得
query {
user(id: 123) {
name
email
posts {
title
createdAt
}
}
}
GraphQLのレスポンス
{
"data": {
"user": {
"name": "田中太郎",
"email": "tanaka@example.com",
"posts": [
{
"title": "GraphQL入門",
"createdAt": "2026-02-01T10:00:00Z"
}
]
}
}
}
主要な違い8つ
1. データ取得の柔軟性
REST:
// 複数のエンドポイントを叩く必要がある
const user = await fetch('/users/123').then(r => r.json());
const posts = await fetch('/users/123/posts').then(r => r.json());
const comments = await fetch('/posts/456/comments').then(r => r.json());
// Over-fetching(余計なデータも取得)
// user.createdAt, user.updatedAt など不要なフィールドも含まれる
GraphQL:
// 1回のリクエストで必要なデータだけ取得
const { data } = await client.query({
query: gql`
query {
user(id: 123) {
name
posts {
title
comments {
content
author { name }
}
}
}
}
`
});
// 必要なフィールドだけ含まれる
2. エンドポイント構成
REST:
// エンドポイントが増える
/api/users
/api/users/:id
/api/users/:id/posts
/api/posts
/api/posts/:id
/api/posts/:id/comments
/api/comments
GraphQL:
// 単一エンドポイント
/graphql
// すべての操作がこのエンドポイントに集約
3. バージョニング
REST:
// URLでバージョン管理
/api/v1/users
/api/v2/users
// または
/api/users (Header: API-Version: 2)
GraphQL:
# フィールドの追加や非推奨化で対応
type User {
name: String
email: String
# 新フィールド追加
phoneNumber: String
# 非推奨フィールド
oldField: String @deprecated(reason: "Use newField instead")
newField: String
}
4. エラーハンドリング
REST:
// HTTPステータスコードでエラー表現
try {
const response = await fetch('/api/users/999');
if (!response.ok) {
if (response.status === 404) {
console.error('User not found');
} else if (response.status === 500) {
console.error('Server error');
}
}
} catch (error) {
console.error('Network error');
}
GraphQL:
// レスポンスにerrorsフィールドを含む
{
"data": {
"user": null
},
"errors": [
{
"message": "User not found",
"path": ["user"],
"extensions": {
"code": "USER_NOT_FOUND"
}
}
]
}
// 部分的成功も可能
{
"data": {
"user": {
"name": "田中太郎",
"posts": null // ここだけエラー
}
},
"errors": [
{
"message": "Posts fetch failed",
"path": ["user", "posts"]
}
]
}
5. キャッシング
REST:
// HTTPキャッシュが使える
GET /api/users/123
Cache-Control: max-age=3600
ETag: "abc123"
// ブラウザやCDNで自動的にキャッシュされる
GraphQL:
// 標準的なHTTPキャッシュが使いにくい(POSTリクエストのため)
// Apollo ClientやRelayなどのクライアントキャッシュが必要
import { InMemoryCache } from '@apollo/client';
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
user: {
read(existing, { args, toReference }) {
return existing || toReference({ __typename: 'User', id: args.id });
}
}
}
}
}
});
6. リアルタイム通信
REST:
// WebSocketやSSEを別途実装
const eventSource = new EventSource('/api/notifications');
eventSource.onmessage = (event) => {
console.log(JSON.parse(event.data));
};
GraphQL:
# Subscriptionが標準機能
subscription {
newPost {
id
title
author { name }
}
}
// Apollo Clientで使用
const { data } = useSubscription(gql`
subscription {
newPost {
id
title
}
}
`);
7. 型システムとドキュメント
REST:
// OpenAPI (Swagger) で定義
openapi: 3.0.0
paths:
/users/{id}:
get:
summary: Get user by ID
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/User'
GraphQL:
# スキーマが自己文書化
"""
ユーザー情報を取得
"""
type Query {
"""
IDでユーザーを取得
"""
user(id: ID!): User
}
type User {
"ユーザーID"
id: ID!
"ユーザー名"
name: String!
"メールアドレス"
email: String!
}
GraphQLはイントロスペクションで型情報を取得できます。
# スキーマ全体を取得
{
__schema {
types {
name
fields {
name
type { name }
}
}
}
}
8. 開発体験
REST:
// Postmanやcurlでテスト
curl -X GET http://localhost:3000/api/users/123
// TypeScript型定義は手動で作成
type User = {
id: number;
name: string;
email: string;
};
GraphQL:
// GraphQL Playgroundで対話的にテスト
// 自動補完、ドキュメント表示が標準装備
// TypeScript型は自動生成
import { gql } from '@apollo/client';
import { GetUserQuery, GetUserQueryVariables } from './__generated__/types';
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
name
email
}
}
`;
// 型安全なクエリ
const { data } = useQuery<GetUserQuery, GetUserQueryVariables>(GET_USER, {
variables: { id: '123' }
});
パフォーマンス比較
N+1問題
REST:
// N+1問題が発生しやすい
const users = await fetch('/api/users').then(r => r.json());
// N回のリクエスト
for (const user of users) {
const posts = await fetch(`/api/users/${user.id}/posts`).then(r => r.json());
}
GraphQL:
// DataLoaderで解決
import DataLoader from 'dataloader';
const userLoader = new DataLoader(async (userIds) => {
const users = await db.users.findMany({
where: { id: { in: userIds } }
});
return userIds.map(id => users.find(u => u.id === id));
});
// リゾルバー
const resolvers = {
Post: {
author: (post) => userLoader.load(post.authorId)
}
};
レスポンスサイズ
REST Over-fetching:
// 必要なのはnameだけだが、すべてのフィールドが返る
{
"id": 123,
"name": "田中太郎",
"email": "tanaka@example.com",
"createdAt": "2026-01-15T10:00:00Z",
"updatedAt": "2026-02-01T10:00:00Z",
"profile": { ... },
"settings": { ... }
}
GraphQL 最適化:
// 必要なフィールドだけ
{
"data": {
"user": {
"name": "田中太郎"
}
}
}
実際のプロジェクトでは、GraphQLでレスポンスサイズが30-50%削減されることが多いです。
実装の複雑さ比較
REST APIサーバーの実装
// Express.jsでのREST API
import express from 'express';
const app = express();
app.get('/api/users', async (req, res) => {
const users = await db.users.findMany();
res.json(users);
});
app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findUnique({
where: { id: parseInt(req.params.id) }
});
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
app.post('/api/users', async (req, res) => {
const user = await db.users.create({
data: req.body
});
res.status(201).json(user);
});
app.listen(3000);
シンプルで理解しやすいです。
GraphQLサーバーの実装
// Apollo Serverでの実装
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
const typeDefs = `
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
author: User!
}
type Query {
user(id: ID!): User
users: [User!]!
}
type Mutation {
createUser(name: String!, email: String!): User!
}
`;
const resolvers = {
Query: {
user: async (_, { id }) => {
return await db.users.findUnique({ where: { id } });
},
users: async () => {
return await db.users.findMany();
}
},
Mutation: {
createUser: async (_, { name, email }) => {
return await db.users.create({ data: { name, email } });
}
},
User: {
posts: async (user) => {
return await db.posts.findMany({ where: { authorId: user.id } });
}
},
Post: {
author: async (post) => {
return await db.users.findUnique({ where: { id: post.authorId } });
}
}
};
const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, { listen: { port: 4000 } });
console.log(`Server ready at ${url}`);
初期構築は複雑ですが、スケールしやすい構造です。
どちらを選ぶべきか?判断基準
RESTを選ぶべきケース
- シンプルなCRUD操作が中心
// ブログのようなシンプルなアプリ
GET /posts
POST /posts
PUT /posts/:id
DELETE /posts/:id
- HTTPキャッシュを最大限活用したい
// CDNでキャッシュさせたい公開API
GET /api/articles // Cache-Control: max-age=3600
- チームのGraphQL習熟度が低い
- 学習コストを避けたい小規模チーム
- 短期間でリリースする必要がある
- 外部APIとして公開
// パートナー企業に提供するAPI
// RESTの方が広く理解されている
- ファイルアップロード・ダウンロードが主要機能
POST /api/upload
GET /api/files/:id/download
GraphQLを選ぶべきケース
- 複雑なデータ取得が必要
# ネストした関連データを1回で取得
query {
user(id: 123) {
name
posts {
title
comments {
content
author { name }
}
}
followers {
name
profile { avatar }
}
}
}
- モバイルアプリなど、通信量を減らしたい
- Over-fetchingの削減が重要
- ネットワークが不安定な環境
- 複数のフロントエンドがある
// Web、iOS、Android、管理画面
// それぞれ必要なデータが異なる
// GraphQLなら1つのAPIで対応可能
- リアルタイム機能が必要
subscription {
messageAdded(roomId: "123") {
id
content
sender { name }
}
}
- 型安全性を重視
// TypeScript型の自動生成
// フロントエンドとバックエンドの型一致を保証
- APIの進化が頻繁
# フィールド追加が容易
# バージョニング不要
type User {
name: String!
# 後から追加
phoneNumber: String
}
ハイブリッドアプローチ
実際には、両方を併用するのも有効です。
// REST: ファイル操作
POST /api/upload
GET /api/files/:id
// GraphQL: データ取得・更新
POST /graphql
移行戦略
RESTからGraphQLへの段階的移行
// フェーズ1: GraphQL導入(RESTと並行運用)
app.use('/api/v1', restRouter); // 既存REST
app.use('/graphql', apolloServer); // 新規GraphQL
// フェーズ2: 新機能はGraphQLで実装
// 既存エンドポイントは維持
// フェーズ3: RESTエンドポイントを徐々にGraphQLに置き換え
// 非推奨警告を出す
app.get('/api/v1/users', (req, res) => {
res.setHeader('Deprecation', 'true');
res.setHeader('Sunset', '2026-12-31');
// ...
});
// フェーズ4: REST完全廃止
実践的なツールと開発体験
REST開発ツール
Postman / Insomnia
- リクエストのテスト・保存
- 環境変数管理
- チームでのコレクション共有
Swagger UI
- OpenAPI定義からドキュメント自動生成
- APIの試用が可能
GraphQL開発ツール
GraphQL Playground / Apollo Studio
- 対話的なクエリ実行
- リアルタイム補完
- スキーマドキュメント自動表示
GraphQL Code Generator
npm install -D @graphql-codegen/cli
# TypeScript型を自動生成
npx graphql-codegen --config codegen.yml
# codegen.yml
schema: http://localhost:4000/graphql
documents: 'src/**/*.graphql'
generates:
src/__generated__/types.ts:
plugins:
- typescript
- typescript-operations
- typescript-react-apollo
DevToolBox (https://devtoolbox.app) GraphQLスキーマのフォーマット、バリデーション、TypeScript型への変換など、API開発を効率化するツールが揃っています。
セキュリティ考慮事項
REST
// Rate limiting
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100
});
app.use('/api/', limiter);
GraphQL
// Query depth制限(ネストした攻撃を防ぐ)
import depthLimit from 'graphql-depth-limit';
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [depthLimit(5)]
});
// Query complexity制限
import { createComplexityLimitRule } from 'graphql-validation-complexity';
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
createComplexityLimitRule(1000)
]
});
まとめ
GraphQLとRESTの選択は、プロジェクトの特性次第です。
REST推奨
- シンプルなCRUD
- HTTPキャッシュ重視
- ファイル操作中心
- 小規模チーム
GraphQL推奨
- 複雑なデータ取得
- モバイル最適化
- リアルタイム機能
- 型安全性重視
2026年のトレンド: 大規模アプリではGraphQLの採用が増えていますが、RESTも依然として主流です。どちらか一方に固執せず、ハイブリッドアプローチも検討しましょう。
API開発に役立つツールを探しているなら、DevToolBoxもチェックしてみてください。GraphQLスキーマのバリデーションやREST APIのテストに使えるツールが揃っています。