REST API Pagination: An In-Depth Guide

Rebecca Kovács

Rebecca Kovács

7 June 2025

REST API Pagination: An In-Depth Guide

In the world of modern application development, REST APIs serve as the fundamental communication layer, enabling disparate systems to exchange data seamlessly. As applications grow in scale and complexity, so does the volume of data they handle. Requesting an entire dataset, potentially containing millions or even billions of records, in a single API call is inefficient, unreliable, and a significant performance bottleneck. This is where a crucial technique in API design and development comes into play: REST API pagination. This guide provides a deep, comprehensive overview of implementing pagination in REST APIs, covering everything from fundamental concepts to advanced real-world implementations using various technology stacks like Node.js, Python, and .NET.

💡
Want a great API Testing tool that generates beautiful API Documentation?

Want an integrated, All-in-One platform for your Developer Team to work together with maximum productivity?

Apidog delivers all your demans, and replaces Postman at a much more affordable price!
button

The Fundamentals of REST API Pagination

Before diving into complex code examples and design patterns, it's essential to have a solid grasp of what pagination is and why it's a non-negotiable aspect of professional API design.

What is Pagination in REST APIs?

At its core, REST API pagination is a technique used to take a REST API endpoint's response and segment it into smaller, more manageable units, often called "pages." Instead of delivering a potentially massive dataset in one go, the API returns a small, predictable chunk of the data. Crucially, the API response also includes metadata that allows a client to incrementally fetch subsequent chunks if they need more data.

This process is analogous to the pages of a book or the search results on Google. You are presented with the first page of results, along with controls to navigate to the second, third, and so on. As developer communities like DEV Community and platforms such as Merge.dev point out, this is the process of breaking down a large dataset into smaller chunks, which can be fetched incrementally by a client if they really want all that data. It's a foundational concept for building robust and scalable applications.

Why is Pagination a Core Requirement in Modern API Design?

The primary motivation for pagination is to ensure API responses are easier to handle for both the server and the client. Without it, applications would face severe limitations and a poor user experience. The key benefits include:

Common Pagination Strategies and Techniques

There are several ways to implement pagination, but two primary strategies have become the de facto standards in the industry. The choice between them has significant implications for performance, data consistency, and user experience.

Offset-Based Pagination: The Foundational Approach

Offset-based pagination, often called "page-number pagination," is frequently the first approach developers learn. It's conceptually simple and seen in many web applications. It works by using two main parameters:

A typical request looks like this: GET /api/products?limit=25&offset=50

This would translate to a SQL query like:SQL

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

This query skips the first 50 products and retrieves the next 25 (i.e., products 51-75).

Pros:

Cons and Limitations:

Cursor-Based (Keyset) Pagination: The Scalable Solution

Cursor-based pagination, also known as keyset or seek pagination, solves the performance and consistency problems of the offset method. Instead of a page number, it uses a "cursor," which is a stable, opaque pointer to a specific record in the dataset.

The flow is as follows:

  1. The client makes an initial request for a page of data.
  2. The server returns the page of data, along with a cursor that points to the last item in that set.
  3. For the next page, the client sends that cursor back to the server.
  4. The server then retrieves records that come after that specific cursor, effectively "seeking" to that point in the dataset.

The cursor is typically an encoded value derived from the column(s) being sorted on. For example, if sorting by created_at (a timestamp), the cursor could be the timestamp of the last record. To handle ties, a second, unique column (like the record's id) is often included.

A request using a cursor looks like this: GET /api/products?limit=25&after_cursor=eyJjcmVhdGVkX2F0IjoiMjAyNS0wNi0wN1QxODowMDowMC4wMDBaIiwiaWQiOjg0N30=

This would translate to a much more performant SQL query:SQL

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

This query uses an index on (created_at, id) to instantly "seek" to the correct starting point, avoiding a full table scan and making it consistently fast regardless of how deep the user is paginating.

Pros:

Cons:

A Comparison of the Two Main Types of Pagination

Choosing between offset and cursor pagination depends entirely on the use case.

FeatureOffset PaginationCursor Pagination
PerformancePoor for deep pages in large datasets.Excellent and consistent at any depth.
Data ConsistencyProne to missing/repeating data (page drift).High; new data does not affect pagination.
NavigationCan jump to any page.Limited to next/previous pages.
ImplementationSimple and straightforward.More complex; requires cursor logic.
Ideal Use CaseSmall, static datasets; admin UIs.Infinite scroll feeds; large, dynamic datasets.

Implementation Best Practices for Server-Side Pagination

Regardless of the chosen strategy, adhering to a set of best practices will result in a clean, predictable, and easy-to-use API. This is often a key part of answering "What is the best practice of server-side pagination?".

Designing the Pagination Response Payload

A common mistake is to return just an array of results. A well-designed pagination response payload should be an object that "envelops" the data and includes clear pagination metadata.JSON

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

For offset pagination, the metadata would look different:JSON

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

This structure makes it trivial for the client to know if there is more data to fetch or to render UI controls.

A core principle of REST is HATEOAS (Hypermedia as the Engine of Application State). This means the API should provide clients with links to navigate to other resources or actions. For pagination, this is incredibly powerful. As demonstrated in the GitHub Docs, a standardized way to do this is with the Link HTTP header.

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

Alternatively, these links can be placed directly in the JSON response body, which is often easier for JavaScript clients to consume:JSON

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

This frees the client from having to construct URLs manually.

Allowing Clients to Control Page Size

It's good practice to allow clients to request additional pages of results for paginated responses and also to change the number of results returned on each page. This is typically done with a limit or per_page query parameter. However, the server should always enforce a reasonable maximum limit (e.g., 100) to prevent clients from requesting too much data at once and overloading the system.

Combining Pagination with Filtering and Sorting

Real-world APIs rarely just paginate; they also need to support filtering and sorting. As shown in tutorials covering technologies like .NET, adding these features is a common requirement.

A complex request might look like: GET /api/products?status=published&sort=-created_at&limit=50&page=2

When implementing this, it's crucial that the filtering and sorting parameters are considered part of the pagination logic. The sort order must be stable and deterministic for pagination to work correctly. If the sort order is not unique, you must add a unique tie-breaker column (like id) to ensure consistent ordering between pages.

Real-World Implementation Examples

Let's explore how to implement these concepts in various popular frameworks.

REST API Pagination in Python with Django REST Framework

One of the most popular combinations for building APIs is Python with the Django REST Framework (DRF). DRF provides powerful, built-in support for pagination, making it incredibly easy to get started. It offers classes for different strategies:

You can configure a default pagination style globally and then simply use a generic ListAPIView, and DRF handles the rest. This is a prime Rest api pagination python example.Python

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

# In your views.py
class ProductListView(generics.ListAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    # DRF handles the entire pagination logic automatically!

Building a Paginated REST API with Node.js, Express, and TypeScript

In the Node.js ecosystem, you often build pagination logic manually, which gives you full control. This guide section provides a conceptual overview of building pagination with Node.js, Express, and TypeScript.

Here is a simplified example of implementing cursor pagination:TypeScript

// In your Express controller
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'));
    // Add the WHERE clause for the cursor
    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 }
  });
});

Pagination in a Java or .NET Ecosystem

Frameworks in other ecosystems also provide robust pagination support.

A Real-World Use Case: Paginating a Product Catalog API

Consider an e-commerce website with a "Product Catalog API." This is a perfect real-world use case. The catalog is large and dynamic, with new products being added frequently.

Advanced Topics and Common Problems

As developers on Stack Overflow and Reddit often discover, building a truly robust pagination system requires handling many details and edge cases.

How to Ensure Data Consistency in a Paginated API

This is one of the most critical advanced topics. As discussed, the only reliable way to guarantee data consistency in a system with frequent writes is to use keyset/cursor pagination. Its design inherently prevents page drift. If for some reason you are stuck with offset pagination, some complex workarounds exist, such as creating a temporary, immutable snapshot of the IDs for the full result set and paginating through that list, but this is highly stateful and generally not recommended for REST APIs.

Handling Strange Edge Cases

A production-ready API must gracefully handle bad input. Consider these common edge cases:

Client-Side Implementation

The client side is where the pagination logic is consumed. Using JavaScript to fetch paginated data from a REST API like a pro involves reading the pagination metadata and using it to make subsequent requests.

Here's a simple fetch example for a "Load More" button using cursor pagination:JavaScript

const loadMoreButton = document.getElementById('load-more');
let nextCursor = null; // Store the cursor globally or in component state

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

  // ... render the new products ...
  nextCursor = data.pagination.next_cursor;

  if (!nextCursor) {
    loadMoreButton.disabled = true; // No more pages
  }
}

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

// Initial load
fetchProducts(null);

The Future of API Data Retrieval and Pagination Standards

While REST has been dominant for years, the landscape is always evolving.

Evolving REST API Pagination Standards

There is no single, formal RFC that defines REST API pagination standards. However, a set of strong conventions has emerged, driven by the public APIs of major tech companies like GitHub, Stripe, and Atlassian. These conventions, such as using the Link header and providing clear metadata, have become the de facto standard. Consistency is key; a well-designed API platform will use the same pagination strategy across all its list-based endpoints.

The Impact of GraphQL on Pagination

GraphQL presents a different paradigm. Instead of multiple endpoints, it has a single endpoint where clients send complex queries specifying the exact data they need. However, the need to paginate large lists of data doesn't disappear. The GraphQL community has also standardized on cursor-based pagination through a formal specification called the Relay Cursor Connections Spec. This defines a precise structure for paginating data, using concepts like first, after, last, and before to provide robust forward and backward pagination.

Conclusion: A Summary of Pagination Best Practices

Mastering REST API pagination is a critical skill for any backend developer. It is a technique essential for building scalable, performant, and user-friendly applications.

To summarize the REST API pagination best practices:

  1. Always Paginate: Never return an unbounded list of results from an API endpoint.
  2. Choose the Right Strategy: Use simple offset pagination for small, non-critical, or static datasets. For anything large, dynamic, or user-facing, strongly prefer cursor-based pagination for its superior performance and data consistency.
  3. Provide Clear Metadata: Your response payload should always include information that tells the client how to get the next page of data, whether it's a next_cursor or page numbers and links.
  4. Use Hypermedia: Use the Link header or links within your JSON body to make your API more discoverable and easier to use.
  5. Handle Errors Gracefully: Validate all pagination parameters and return clear 400 Bad Request errors for invalid input.

By following this guide and internalizing these principles, you can design and build professional, production-ready REST APIs that can scale effectively to meet any demand.

REST API Pagination FAQs

1. What is the main difference between offset and cursor pagination?

The main difference lies in how they determine which set of data to retrieve. Offset pagination uses a numerical offset (like "skip the first 50 items") to find the next page. This can be slow for large datasets because the database still has to count through the items being skipped. Cursor pagination uses a stable pointer or "cursor" that points to a specific record (like "get items after product ID 857"). This is much more efficient because the database can use an index to jump directly to that record.

2. When is it appropriate to use offset pagination instead of cursor pagination?

Offset pagination is appropriate for datasets that are small, not performance-critical, or do not change often. Its primary advantage is simplicity and the ability for a user to jump to any specific page number (e.g., "Go to Page 10"). This makes it suitable for things like admin dashboards or internal tools where the user experience of jumping between pages is more important than handling real-time data changes.

3. How does cursor-based pagination prevent the problem of skipping or repeating items?

Cursor-based pagination prevents data inconsistency because it anchors the next request to a specific item, not a numerical position. For example, if you request the page after the item with ID=100, it doesn't matter if new items are added before it; the query will always start fetching from the correct place. With offset pagination, if a new item is added to page 1 while you are viewing it, when you request page 2, the last item from page 1 will now be the first item on page 2, causing a repeat.

4. Is there an official standard for REST API pagination responses?

There is no single, official RFC or formal standard that dictates how all REST API pagination must be implemented. However, strong conventions and best practices have emerged from the industry, largely set by major public APIs like those from GitHub and Stripe. These conventions include using a Link HTTP header with rel="next" and rel="prev" attributes, or embedding a pagination object with clear metadata and links directly in the JSON response body.

5. How should I handle sorting and filtering with my paginated endpoints?

Sorting and filtering should be applied before pagination. The paginated results should be a "view" into the already sorted and filtered dataset. It's crucial that the sort order is stable and deterministic. If a user sorts by a non-unique field (like a date), you must add a unique secondary sort key (like a record's id) to act as a tie-breaker. This ensures that the order of items is always the same, which is essential for both offset and cursor pagination to work correctly.

💡
Want a great API Testing tool that generates beautiful API Documentation?

Want an integrated, All-in-One platform for your Developer Team to work together with maximum productivity?

Apidog delivers all your demans, and replaces Postman at a much more affordable price!
button

Explore more

How to Use the new Gemini 2.5 06-05 Pro API

How to Use the new Gemini 2.5 06-05 Pro API

Learn how to use the Gemini 2.5 06-05 Pro API in this technical guide. Set up, authenticate, and explore multimodal and coding features of Google’s powerful AI model. Perfect for developers building advanced apps!

6 June 2025

How to Use Deepseek R1 Locally with Cursor

How to Use Deepseek R1 Locally with Cursor

Learn how to set up and configure local DeepSeek R1 with Cursor IDE for private, cost-effective AI coding assistance.

4 June 2025

How to Run Gemma 3n on Android ?

How to Run Gemma 3n on Android ?

Learn how to install and run Gemma 3n on Android using Google AI Edge Gallery.

3 June 2025

Practice API Design-first in Apidog

Discover an easier way to build and use APIs