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を選ぶべきケース

  1. シンプルなCRUD操作が中心
// ブログのようなシンプルなアプリ
GET /posts
POST /posts
PUT /posts/:id
DELETE /posts/:id
  1. HTTPキャッシュを最大限活用したい
// CDNでキャッシュさせたい公開API
GET /api/articles  // Cache-Control: max-age=3600
  1. チームのGraphQL習熟度が低い
  • 学習コストを避けたい小規模チーム
  • 短期間でリリースする必要がある
  1. 外部APIとして公開
// パートナー企業に提供するAPI
// RESTの方が広く理解されている
  1. ファイルアップロード・ダウンロードが主要機能
POST /api/upload
GET /api/files/:id/download

GraphQLを選ぶべきケース

  1. 複雑なデータ取得が必要
# ネストした関連データを1回で取得
query {
  user(id: 123) {
    name
    posts {
      title
      comments {
        content
        author { name }
      }
    }
    followers {
      name
      profile { avatar }
    }
  }
}
  1. モバイルアプリなど、通信量を減らしたい
  • Over-fetchingの削減が重要
  • ネットワークが不安定な環境
  1. 複数のフロントエンドがある
// Web、iOS、Android、管理画面
// それぞれ必要なデータが異なる
// GraphQLなら1つのAPIで対応可能
  1. リアルタイム機能が必要
subscription {
  messageAdded(roomId: "123") {
    id
    content
    sender { name }
  }
}
  1. 型安全性を重視
// TypeScript型の自動生成
// フロントエンドとバックエンドの型一致を保証
  1. 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のテストに使えるツールが揃っています。