REST API vs GraphQL:プロジェクトに適した方式を選ぶ判断軸

REST APIとGraphQLは両者とも有効なAPI設計パラダイムですが、プロジェクト特性により最適な選択は異なります。本記事では、両者の具体的な違いを実装レベルで比較し、どのプロジェクト特性でどちらを選ぶべきかの実践的な判断軸を解説します。

REST APIとGraphQLの本質的な違い

REST APIはリソース中心の設計哲学です。各エンドポイント(例:/users/123/posts/456)がリソースに対応し、HTTPメソッド(GET、POST、PUT、DELETE)で操作を定義します。

一方、GraphQLはクエリ言語です。単一のエンドポイント(例:/graphql)に対して、クライアント側で必要なデータを明示的に指定します。サーバーは指定されたフィールドのみを返す、という根本的に異なるアプローチです。

具体例で見る データ取得の流れ

REST APIの場合:

// ユーザー情報を取得
GET /api/users/123
→ { id, name, email, profile, posts, followers, ... } すべて返される

// さらに詳細な投稿情報が必要な場合
GET /api/users/123/posts
→ 別途リクエストが必要(N+1問題が発生しやすい)

GraphQLの場合:

// 必要なフィールドのみ指定
query {
  user(id: 123) {
    name
    email
    posts {
      title
      createdAt
    }
  }
}
→ { name, email, posts[{ title, createdAt }] } 指定した項目のみ返される

REST APIが向いているプロジェクト

シンプルなCRUD操作が中心のサービス

ユーザー管理、ブログシステム、基本的なe-commerceなど、データモデルが単純で階層が浅いプロジェクトではREST APIで十分です。実装が直感的で、開発速度が速くなります。

キャッシング戦略を活用したい場合

REST APIはHTTPメソッドとステータスコードに基づくキャッシングが成熟しています。CDNやプロキシサーバーレベルでの標準的なキャッシュ戦略が使える点が大きな利点です。GraphQLではPOSTリクエストが多く、キャッシングが複雑になります。

チームがGraphQLの学習コストを負担したくない場合

REST APIは業界スタンダードで、ほぼすべての開発者が理解しています。オンボーディング期間が短く、メンテナンスも容易です。

GraphQLが向いているプロジェクト

複雑で階層的なデータ関係がある場合

ユーザー → フォロワー → フォロワーの投稿 → 投稿のコメント、といった多層的なデータ取得が頻繁に発生するプロジェクトでは、GraphQLの1回のクエリで必要なデータをすべて取得できる利点が際立ちます。

// 複雑なデータ構造を1回のクエリで取得
query {
  user(id: 123) {
    name
    followers(first: 10) {
      edges {
        node {
          name
          posts(first: 5) {
            edges {
              node {
                title
                comments(first: 3) {
                  totalCount
                  edges {
                    node {
                      text
                      author {
                        name
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

複数のクライアント(Web、モバイル、デスクトップ)への対応

デバイスごとに異なるデータ要件がある場合、GraphQLならクライアント側でクエリを調整するだけで対応できます。REST APIでは複数のエンドポイントやバージョン管理が必要になります。

オーバーフェッチング・アンダーフェッチングを最小化したい場合

オーバーフェッチング:REST APIで不要なフィールドまで取得されている状態。例えば、ユーザーの名前だけが必要なのに、プロフィール全体が返される。

アンダーフェッチング:1回のリクエストで必要なデータが不足している状態。複数回のリクエストが必要になります。

GraphQLはクライアント指定なので、両問題を根本的に排除できます。特にモバイルアプリでネットワーク帯域幅が制限される環境では威力を発揮します。

実装の難易度と運用コスト

REST API実装例(Node.js + Express)

// シンプルで理解しやすい
const express = require('express');
const app = express();

app.get('/api/users/:id', (req, res) => {
  const userId = req.params.id;
  const user = getUserFromDB(userId);
  res.json(user);
});

app.post('/api/users', (req, res) => {
  const newUser = createUser(req.body);
  res.status(201).json(newUser);
});

app.put('/api/users/:id', (req, res) => {
  const updatedUser = updateUser(req.params.id, req.body);
  res.json(updatedUser);
});

app.delete('/api/users/:id', (req, res) => {
  deleteUser(req.params.id);
  res.status(204).send();
});

GraphQL実装例(Node.js + Apollo Server)

const { ApolloServer, gql } = require('apollo-server');

// スキーマ定義(型安全性が得られる)
const typeDefs = gql`
  type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
  }
  
  type Post {
    id: ID!
    title: String!
    author: User!
  }
  
  type Query {
    user(id: ID!): User
    posts: [Post!]!
  }
  
  type Mutation {
    createUser(name: String!, email: String!): User
    updateUser(id: ID!, name: String): User
    deleteUser(id: ID!): Boolean
  }
`;

// リゾルバー定義
const resolvers = {
  Query: {
    user: (_, { id }) => getUserFromDB(id),
    posts: () => getPostsFromDB(),
  },
  Mutation: {
    createUser: (_, { name, email }) => createUserInDB(name, email),
    updateUser: (_, { id, name }) => updateUserInDB(id, name),
    deleteUser: (_, { id }) => deleteUserFromDB(id),
  },
  User: {
    posts: (user) => getPostsByUserId(user.id), // フィールドレベルのリゾルバー
  },
};

const server = new ApolloServer({ typeDefs, resolvers });
server.listen();

運用の観点での違い

REST API:新しいフィールドが必要になると、既存エンドポイントの返却値が増える可能性があり、クライアント側で不要なデータを無視する必要があります。APIのバージョン管理(v1, v2など)が必要になることもあります。

GraphQLスキーマが単一の真実のソース(Single Source of Truth)になり、型安全性が得られます。新しいフィールド追加もスキーマを拡張するだけで、既存クエリに影響しません。ただし、スキーマ設計の初期段階が複雑です。

パフォーマンス特性の実際

ネットワークラウンドトリップ数

REST APIで階層的データを取得する場合、複数のエンドポイントへのリクエストが必要になり、ラウンドトリップが増加します。GraphQLは1回のリクエストでツリー構造のデータをまとめて取得できます。

シナリオ REST API GraphQL
ユーザー情報のみ取得 1リクエスト 1リクエスト
ユーザー + 投稿 + コメント取得 3-5リクエスト 1リクエスト
不要なフィールドがある場合 全フィールド転送(オーバーフェッチ) 指定フィールドのみ転送

クエリの複雑さによる落とし穴

GraphQLの便利さの裏返しとして、クライアント側で非常に複雑で深いクエリを書くことができます。これがサーバー側に過負荷をかける可能性があります。

// 悪い例:深いネストが無限に続く可能性
query {
  user(id: 1) {
    followers(first: 1000) { // 1000件
      edges {
        node {
          followers(first: 1000) { // さらに1000件
            edges {
              node {
                followers(first: 1000) { // さらに...
                  // クエリが複雑化しサーバー負荷増加
                }
              }
            }
          }
        }
      }
    }
  }
}

対策:Apollo ServerではdepthLimitcostAnalysisというセキュリティ機構を設定し、複雑なクエリを制限できます。

セキュリティとキャッシング

REST APIのセキュリティ優位性

REST APIはHTTPメソッドで操作が明確に区別されるため、ファイアウォール・WAFレベルでのセキュリティルール設定が直感的です。GET以外のリクエストを厳格に制限するといった戦略が容易です。

GraphQLのセキュリティ課題

ほぼすべてのGraphQLリクエストがPOSTメソッドで送信されるため、従来のHTTPレベルのセキュリティルールが効きにくいという課題があります。クエリインジェクションやDoS攻撃(複雑なクエリによる過負荷)への対策が必須です。

// GraphQL DoS対策の例
const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: {
    didResolveOperation(context) {
      const complexity = getQueryComplexity(context.document);
      if (complexity > MAX_QUERY_COMPLEXITY) {
        throw new Error('クエリが複雑すぎます');
      }
    },
  },
});

キャッシング戦略の実装難易度

REST APIはCache-ControlヘッダーやETags、条件付きリクエストが標準的に機能します。CDNでの自動キャッシュも容易です。

GraphQLはクエリがPOSTで送信されるため、HTTPキャッシュが効きません。Apollo Clientなどのクライアント側キャッシュや、Redis等を使用したサーバー側キャッシュを実装する必要があります。

ハマりやすいポイントと解決策

N+1問題(REST API)

問題:ユーザーリストを取得した後、各ユーザーの投稿数を取得するため、ユーザー数分のクエリが実行される。

// ハマりやすいコード
const users = await User.find(); // 1クエリ
const usersWithPosts = await Promise.all(
  users.map(user => 
    Post.count({ userId: user.id }) // N クエリ(ユーザー数分実行)
  )
);
// 合計 N+1 クエリが実行される
    
K
AWS・Python・生成AIを専門とするソフトウェアエンジニア。AI・クラウド・開発ワークフローの実践ガイドを執筆しています。詳しく見る →