REST API ページネーション:徹底解説

Rebecca Kovács

Rebecca Kovács

7 6月 2025

REST API ページネーション:徹底解説

現代のアプリケーション開発の世界では、REST APIは基本的な通信レイヤーとして機能し、異なるシステム間でのデータのシームレスな交換を可能にしています。アプリケーションの規模と複雑さが増すにつれて、扱うデータの量も増加します。数百万、あるいは数十億のレコードを含む可能性のあるデータセット全体を単一のAPIコールで要求することは、非効率的で信頼性が低く、重大なパフォーマンスボトルネックとなります。ここで、API設計と開発における重要なテクニックが登場します。それがREST APIのページネーションです。このガイドでは、REST APIにおけるページネーションの実装について、基本的な概念からNode.js、Python、.NETなどの様々なテクノロジースタックを使用した高度な実践的な実装まで、深く包括的な概要を提供します。

💡
美しいAPIドキュメントを生成する優れたAPIテストツールをお探しですか?

最大限の生産性で開発チームが協力して作業できる、統合されたオールインワンプラットフォームをお探しですか?

Apidogはこれらの要望をすべて満たし、Postmanよりもはるかに手頃な価格で代替できます!
button

REST APIページネーションの基礎

複雑なコード例や設計パターンに入る前に、ページネーションとは何か、そしてなぜそれがプロフェッショナルなAPI設計において必須の要素なのかをしっかりと理解しておくことが不可欠です。

REST APIにおけるページネーションとは?

本質的に、REST APIページネーションは、REST APIエンドポイントのレスポンスを、より小さく管理しやすい単位(しばしば「ページ」と呼ばれる)に分割するために使用されるテクニックです。潜在的に膨大なデータセットを一度に配信する代わりに、APIは小さく予測可能なデータの塊を返します。重要なのは、APIレスポンスには、クライアントが必要に応じて後続の塊を増分的にフェッチできるようにするメタデータも含まれていることです。

このプロセスは、本のページやGoogleの検索結果に似ています。最初の検索結果ページが表示され、それに加えて2ページ目、3ページ目などへ移動するためのコントロールが提供されます。DEV Communityのような開発者コミュニティやMerge.devのようなプラットフォームが指摘するように、これは大きなデータセットを小さな塊に分割するプロセスであり、クライアントが本当にすべてのデータを必要とする場合に増分的にフェッチできます。これは、堅牢でスケーラブルなアプリケーションを構築するための基礎的な概念です。

なぜページネーションは現代のAPI設計における中核的な要件なのか?

ページネーションの主な動機は、APIレスポンスをサーバーとクライアントの両方にとって扱いやすくすることです。それがなければ、アプリケーションは深刻な制限と貧弱なユーザーエクスペリエンスに直面するでしょう。主な利点は以下の通りです。

一般的なページネーション戦略とテクニック

ページネーションを実装する方法はいくつかありますが、2つの主要な戦略が業界のデファクトスタンダードとなっています。それらの選択は、パフォーマンス、データの一貫性、およびユーザーエクスペリエンスに大きな影響を与えます。

オフセットベースのページネーション:基本的なアプローチ

オフセットベースのページネーションは、「ページ番号ページネーション」とも呼ばれ、開発者が最初に学ぶアプローチであることがよくあります。概念的にシンプルで、多くのウェブアプリケーションで見られます。これは、2つの主要なパラメータを使用して機能します。

一般的なリクエストは次のようになります: GET /api/products?limit=25&offset=50

これは、次のようなSQLクエリに変換されます。SQL

SELECT * FROM products ORDER BY created_at DESC LIMIT 25 OFFSET 50;

このクエリは、最初の50個の製品をスキップし、次の25個(つまり、製品51〜75)を取得します。

長所:

短所と制限:

カーソルベース(キーセット)ページネーション:スケーラブルなソリューション

カーソルベースのページネーションは、キーセットまたはシークページネーションとも呼ばれ、オフセットメソッドのパフォーマンスと一貫性の問題を解決します。ページ番号の代わりに、データセット内の特定のレコードへの安定した不透明なポインターである「カーソル」を使用します。

フローは次のとおりです。

  1. クライアントはデータのページを要求する最初のリクエストを行います。
  2. サーバーはデータのページを返し、そのセットの最後の項目を指すカーソルを含めます。
  3. 次のページのために、クライアントはそのカーソルをサーバーに送り返します。
  4. サーバーは、その特定のカーソルの後に来るレコードを取得し、データセット内のそのポイントに効果的に「シーク」します。

カーソルは通常、ソート対象の列から派生したエンコードされた値です。たとえば、created_at(タイムスタンプ)でソートする場合、カーソルは最後のレコードのタイムスタンプになる可能性があります。タイを処理するために、2番目のユニークな列(レコードのidなど)がしばしば含まれます。

カーソルを使用したリクエストは次のようになります: GET /api/products?limit=25&after_cursor=eyJjcmVhdGVkX2F0IjoiMjAyNS0wNi0wN1QxODowMDowMC4wMDBaIiwiaWQiOjg0N30=

これは、はるかにパフォーマンスの高いSQLクエリに変換されます。SQL

SELECT * FROM products
WHERE (created_at, id) < ('2025-06-07T18:00:00.000Z', 847)
ORDER BY created_at DESC, id DESC
LIMIT 25;

このクエリは、(created_at, id) のインデックスを使用して正しい開始点に瞬時に「シーク」し、フルテーブルスキャンを回避し、ユーザーがどれだけ深くページングしても一貫して高速になります。

長所:

短所:

2つの主要なページネーションタイプの比較

オフセットページネーションとカーソルページネーションのどちらを選択するかは、ユースケースに完全に依存します。

機能オフセットページネーションカーソルページネーション
パフォーマンス大規模データセットの深いページではパフォーマンスが低下します。あらゆる深さで優れており、一貫しています。
データの一貫性データの欠落/重複(ページドリフト)が発生しやすいです。高いです。新しいデータはページネーションに影響しません。
ナビゲーション任意のページにジャンプできます。次/前のページに制限されます。
実装シンプルで分かりやすいです。より複雑です。カーソルロジックが必要です。
理想的なユースケース小規模で静的なデータセット、管理UI。無限スクロールフィード、大規模で動的なデータセット。

サーバーサイドページネーションの実装におけるベストプラクティス

選択した戦略に関わらず、一連のベストプラクティスに従うことで、クリーンで予測可能、かつ使いやすいAPIが実現します。これは、「サーバーサイドページネーションのベストプラクティスとは?」という問いに対する重要な回答の一部となることがよくあります。

ページネーションレスポンスペイロードの設計

よくある間違いは、結果の配列だけを返すことです。適切に設計されたページネーションレスポンスペイロードは、データを「包み込み」、明確なページネーションメタデータを含むオブジェクトであるべきです。JSON

{
  "data": [
    { "id": 101, "name": "Product A" },
    { "id": 102, "name": "Product B" }
  ],
  "pagination": {
    "next_cursor": "eJjcmVhdGVkX2F0Ij...",
    "has_next_page": true
  }
}

オフセットページネーションの場合、メタデータは異なります。JSON

{
  "data": [
    // ... 結果 ...
  ],
  "metadata": {
    "total_results": 8452,
    "total_pages": 339,
    "current_page": 3,
    "per_page": 25
  }
}

この構造により、クライアントはさらにフェッチするデータがあるかどうか、またはUIコントロールをレンダリングする必要があるかどうかを簡単に知ることができます。

ナビゲーションのためのハイパーメディアリンクの使用(HATEOAS)

RESTの核心原則の1つはHATEOAS(Hypermedia as the Engine of Application State)です。これは、APIがクライアントに他のリソースやアクションにナビゲートするためのリンクを提供すべきであることを意味します。ページネーションの場合、これは信じられないほど強力です。GitHub Docsで示されているように、これを行うための標準的な方法は、Link HTTPヘッダーを使用することです。

Link: <https://api.example.com/items?page=3>; rel="next", <https://api.example.com/items?page=1>; rel="prev"

あるいは、これらのリンクをJSONレスポンスボディに直接配置することもできます。これはJavaScriptクライアントが消費しやすい場合が多いです。JSON

"pagination": {
  "links": {
    "next": "https://api.example.com/items?limit=25&offset=75",
    "previous": "https://api.example.com/items?limit=25&offset=25"
  }
}

これにより、クライアントはURLを手動で構築する必要がなくなります。

クライアントによるページサイズの制御を許可する

クライアントがページ分割されたレスポンスに対して追加のページの結果を要求すること、および各ページで返される結果の数を変更することを許可することは良い習慣です。これは通常、limit または per_page クエリパラメータで行われます。ただし、サーバーは常に合理的な最大制限(例:100)を強制し、クライアントが一度に多すぎるデータを要求してシステムを過負荷にしないようにする必要があります。

ページネーションとフィルタリングおよびソートの組み合わせ

実際のAPIはページネーションだけを行うことはめったにありません。フィルタリングとソートもサポートする必要があります。.NETのようなテクノロジーを扱うチュートリアルで示されているように、これらの機能を追加することは一般的な要件です。

複雑なリクエストは次のようになる可能性があります: GET /api/products?status=published&sort=-created_at&limit=50&page=2

これを実装する際には、フィルタリングとソートのパラメータがページネーションロジックの一部として考慮されることが重要です。ページネーションが正しく機能するためには、sort順序が安定しており決定的である必要があります。ソート順序が一意でない場合は、ページ間の一貫した順序付けを確保するために、ユニークなタイブレーカー列(idなど)を追加する必要があります。

実践的な実装例

さまざまな一般的なフレームワークでこれらの概念を実装する方法を見ていきましょう。

Django REST Frameworkを使用したPythonでのREST APIページネーション

API構築で最も人気のある組み合わせの1つは、PythonとDjango REST Framework (DRF)です。DRFは、ページネーションのための強力な組み込みサポートを提供し、開始を信じられないほど容易にします。さまざまな戦略のためのクラスを提供しています。

デフォルトのページネーションスタイルをグローバルに設定し、汎用のListAPIViewを使用するだけで、残りはDRFが処理します。これは、Rest api pagination pythonの主要な例です。Python

# settings.py内
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination',
    'PAGE_SIZE': 50
}

# views.py内
class ProductListView(generics.ListAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    # DRFがページネーションロジック全体を自動的に処理します!

Node.js、Express、およびTypeScriptを使用したページネーションREST APIの構築

Node.jsエコシステムでは、ページネーションロジックを手動で構築することが多く、これにより完全な制御が可能になります。このガイドセクションでは、Node.js、Express、およびTypeScriptを使用したページネーション構築の概念的な概要を提供します。

カーソルページネーションを実装する単純な例を以下に示します。TypeScript

// Expressコントローラー内
app.get('/products', async (req: Request, res: Response) => {
  const limit = parseInt(req.query.limit as string) || 25;
  const cursor = req.query.cursor as string;

  let query = db.selectFrom('products').orderBy('createdAt', 'desc').orderBy('id', 'desc').limit(limit);

  if (cursor) {
    const { createdAt, id } = JSON.parse(Buffer.from(cursor, 'base64').toString('ascii'));
    // カーソルのためのWHERE句を追加
    query = query.where('createdAt', '<=', createdAt).where('id', '<', id);
  }

  const products = await query.execute();

  const nextCursor = products.length > 0
    ? Buffer.from(JSON.stringify({
        createdAt: products[products.length - 1].createdAt,
        id: products[products.length - 1].id
      })).toString('base64')
    : null;

  res.json({
    data: products,
    pagination: { next_cursor: nextCursor }
  });
});

Javaまたは.NETエコシステムにおけるページネーション

他のエコシステムのフレームワークも、堅牢なページネーションサポートを提供しています。

### 実践的なユースケース:製品カタログAPIのページネーション

「製品カタログAPI」を持つEコマースウェブサイトを考えてみましょう。これは完璧な実践的なユースケースです。カタログは大きく動的であり、新しい製品が頻繁に追加されます。

高度なトピックと一般的な問題

Stack OverflowやRedditの開発者がよく発見するように、真に堅牢なページネーションシステムを構築するには、多くの詳細とエッジケースの処理が必要です。

ページ分割されたAPIでデータの一貫性を確保する方法

これは最も重要な高度なトピックの1つです。前述のように、頻繁な書き込みがあるシステムでデータの一貫性を保証する唯一の信頼できる方法は、キーセット/カーソルページネーションを使用することです。その設計は本質的にページドリフトを防ぎます。何らかの理由でオフセットページネーションに固執する必要がある場合、一時的で不変のIDスナップショットを作成し、そのリストをページングするなどの複雑な回避策が存在しますが、これは非常にステートフルであり、一般的にREST APIには推奨されません。

奇妙なエッジケースの処理

プロダクション対応のAPIは、不正な入力を適切に処理する必要があります。これらの一般的なエッジケースを考慮してください。

クライアントサイドの実装

クライアントサイドは、ページネーションロジックが消費される場所です。JavaScriptを使用してプロのようにREST APIからページ分割されたデータをフェッチするには、ページネーションメタデータを読み取り、それを使用して後続のリクエストを行うことが含まれます。

カーソルページネーションを使用した「もっと見る」ボタンの単純なfetch例を以下に示します。JavaScript

const loadMoreButton = document.getElementById('load-more');
let nextCursor = null; // カーソルをグローバルまたはコンポーネントの状態に保存

async function fetchProducts(cursor) {
  const url = cursor ? `/api/products?cursor=${cursor}` : '/api/products';
  const response = await fetch(url);
  const data = await response.json();

  // ... 新しい製品をレンダリング ...
  nextCursor = data.pagination.next_cursor;

  if (!nextCursor) {
    loadMoreButton.disabled = true; // これ以上ページはありません
  }
}

loadMoreButton.addEventListener('click', () => fetchProducts(nextCursor));

// 初回ロード
fetchProducts(null);

APIデータ取得とページネーション標準の未来

RESTは何年も支配的でしたが、状況は常に進化しています。

進化するREST APIページネーション標準

REST APIページネーション標準を定義する単一の正式なRFCはありません。しかし、GitHub、Stripe、Atlassianのような主要なテクノロジー企業の公開APIによって推進される、強力な慣習のセットが出現しています。Linkヘッダーの使用や明確なメタデータの提供といったこれらの慣習は、デファクトスタンダードとなっています。一貫性が鍵であり、適切に設計されたAPIプラットフォームは、すべてのリストベースのエンドポイントで同じページネーション戦略を使用します。

GraphQLがページネーションに与える影響

GraphQLは異なるパラダイムを提示します。複数のエンドポイントの代わりに、クライアントが必要な正確なデータを指定する複雑なクエリを送信する単一のエンドポイントがあります。しかし、大規模なデータリストをページングする必要性はなくなりません。GraphQLコミュニティも、Relay Cursor Connections Specと呼ばれる正式な仕様を通じて、カーソルベースのページネーションを標準化しています。これは、firstafterlastbeforeのような概念を使用して、堅牢な前方および後方ページネーションを提供するデータページネーションの正確な構造を定義します。

結論:ページネーションのベストプラクティスのまとめ

REST APIページネーションを習得することは、あらゆるバックエンド開発者にとって重要なスキルです。これは、スケーラブルでパフォーマンスが高く、ユーザーフレンドリーなアプリケーションを構築するために不可欠なテクニックです。

REST APIページネーションのベストプラクティスをまとめると、以下のようになります。

  1. 常にページネーションを行う: APIエンドポイントから無制限の結果リストを返さないでください。
  2. 適切な戦略を選択する: 小規模で重要でない、または静的なデータセットには単純なオフセットページネーションを使用します。大規模で動的、またはユーザー向けのデータには、優れたパフォーマンスとデータの一貫性のために、カーソルベースのページネーションを強く推奨します。
  3. 明確なメタデータを提供する: レスポンスペイロードには、next_cursorまたはページ番号とリンクなど、クライアントが次のデータページを取得する方法を示す情報が常に含まれている必要があります。
  4. ハイパーメディアを使用する: LinkヘッダーまたはJSONボディ内のリンクを使用して、APIをより発見しやすく、使いやすくします。
  5. エラーを適切に処理する: すべてのページネーションパラメータを検証し、無効な入力に対して明確な400 Bad Requestエラーを返します。

このガイドに従い、これらの原則を内面化することで、あらゆる要求に効果的に対応できるプロフェッショナルでプロダクション対応のREST APIを設計および構築できます。

REST APIページネーションに関するよくある質問

1. オフセットページネーションとカーソルページネーションの主な違いは何ですか?

主な違いは、どのデータセットを取得するかを決定する方法にあります。オフセットページネーションは、数値のオフセット(「最初の50項目をスキップ」など)を使用して次のページを見つけます。これは、データベースがスキップされる項目をカウントする必要があるため、大規模なデータセットでは遅くなる可能性があります。カーソルページネーションは、特定のレコードを指す安定したポインターまたは「カーソル」(「製品ID 857の後の項目を取得」など)を使用します。データベースはインデックスを使用してそのレコードに直接ジャンプできるため、これははるかに効率的です。

2. カーソルページネーションの代わりにオフセットページネーションを使用するのが適切なのはどのような場合ですか?

オフセットページネーションは、データセットが小規模で、パフォーマンスが重要でなく、頻繁に変更されない場合に適しています。その主な利点はシンプルさと、ユーザーが任意の特定のページ番号にジャンプできること(例:「10ページ目に移動」)です。これは、ページ間のジャンプというユーザーエクスペリエンスがリアルタイムデータの変更処理よりも重要な管理ダッシュボードや内部ツールなどに適しています。

3. カーソルベースのページネーションは、項目のスキップや重複の問題をどのように防ぎますか?

カーソルベースのページネーションは、次のリクエストを数値位置ではなく特定の項目に固定するため、データの一貫性を防ぎます。たとえば、ID=100の項目の後のページを要求する場合、その前に新しい項目が追加されても関係ありません。クエリは常に正しい場所からフェッチを開始します。オフセットページネーションでは、表示中に1ページ目に新しい項目が追加されると、2ページ目を要求したときに、1ページ目の最後の項目が2ページ目の最初の項目になり、重複が発生します。

4. REST APIページネーションレスポンスに関する公式な標準はありますか?

すべてのREST APIページネーションがどのように実装されるべきかを規定する単一の公式RFCまたは正式な標準はありません。しかし、GitHubやStripeのような主要な公開APIによって、業界から強力な慣習とベストプラクティスが出現しています。これらの慣習には、rel="next"およびrel="prev"属性を持つLink HTTPヘッダーの使用、またはJSONレスポンスボディに明確なメタデータとリンクを含むpaginationオブジェクトを埋め込むことが含まれます。

5. ページ分割されたエンドポイントでソートとフィルタリングをどのように処理すべきですか?

ソートとフィルタリングはページネーションの前に適用されるべきです。ページ分割された結果は、すでにソートおよびフィルタリングされたデータセットへの「ビュー」であるべきです。ソート順序が安定しており決定的であることが重要です。ユーザーが一意でないフィールド(日付など)でソートする場合、タイブレーカーとして機能する一意のセカンダリソートキー(レコードのidなど)を追加する必要があります。これにより、項目の順序が常に同じになり、オフセットページネーションとカーソルページネーションの両方が正しく機能するために不可欠です。

💡
美しいAPIドキュメントを生成する優れたAPIテストツールをお探しですか?

最大限の生産性で開発チームが協力して作業できる、統合されたオールインワンプラットフォームをお探しですか?

Apidogはこれらの要望をすべて満たし、Postmanよりもはるかに手頃な価格で代替できます!
button

ApidogでAPIデザイン中心のアプローチを取る

APIの開発と利用をよりシンプルなことにする方法を発見できる