Django API Pagination

Breaking large datasets into manageable pages

📄 What is API Pagination?

Pagination divides large API responses into smaller pages, improving performance and user experience. Instead of loading thousands of records at once, you get manageable chunks of data.


# Simple pagination example
from rest_framework.pagination import PageNumberPagination

class StandardPagination(PageNumberPagination):
    page_size = 10
    page_size_query_param = 'page_size'
    max_page_size = 100
                                    

Types of Pagination

🔢

PageNumberPagination

Navigate using page numbers (1, 2, 3...)

?page=2 Simple User-friendly
⏭️

LimitOffsetPagination

Control limit and offset manually

?limit=10 ?offset=20 Flexible
🔗

CursorPagination

Best for large datasets with cursor

Fast Consistent No duplicates
⚙️

Custom Pagination

Create your own pagination style

Customizable Unique needs Advanced

🔹 Setting Up PageNumberPagination

PageNumberPagination is the most common pagination style. It divides results into numbered pages, making it easy for users to navigate through data like browsing a book.

# pagination.py
from rest_framework.pagination import PageNumberPagination

class CustomPagination(PageNumberPagination):
    page_size = 10  # Items per page
    page_size_query_param = 'page_size'  # Allow client to set page size
    max_page_size = 100  # Maximum items per page

# views.py
from rest_framework import generics
from .models import Product
from .serializers import ProductSerializer
from .pagination import CustomPagination

class ProductListView(generics.ListAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    pagination_class = CustomPagination

API Response:

{
  "count": 100,
  "next": "http://api.example.com/products/?page=2",
  "previous": null,
  "results": [
    {"id": 1, "name": "Product 1"},
    {"id": 2, "name": "Product 2"},
    ...
  ]
}

🔹 LimitOffsetPagination

LimitOffsetPagination gives you precise control over which records to fetch. Use 'limit' to specify how many items and 'offset' to skip records, perfect for custom navigation.

# pagination.py
from rest_framework.pagination import LimitOffsetPagination

class CustomLimitOffsetPagination(LimitOffsetPagination):
    default_limit = 10
    max_limit = 50

# views.py
class ProductListView(generics.ListAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    pagination_class = CustomLimitOffsetPagination

# Usage:
# GET /products/?limit=10&offset=20
# Returns 10 items starting from item 21

Query Parameters:

  • limit: Number of items to return
  • offset: Number of items to skip
  • Example: ?limit=5&offset=10 (items 11-15)

🔹 CursorPagination

CursorPagination is ideal for large, frequently updated datasets. It uses a cursor (pointer) to track position, ensuring consistent results even when data changes, preventing duplicate or missing items.

# pagination.py
from rest_framework.pagination import CursorPagination

class ProductCursorPagination(CursorPagination):
    page_size = 10
    ordering = '-created_at'  # Must specify ordering

# views.py
class ProductListView(generics.ListAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    pagination_class = ProductCursorPagination

Benefits:

  • ✅ No duplicate results
  • ✅ Consistent pagination
  • ✅ Better performance for large datasets
  • ❌ Cannot jump to specific page

🔹 Global Pagination Settings

Set default pagination for all API views in your Django settings file:

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10
}

# This applies to all views unless overridden

🔹 Custom Pagination Class

Create custom pagination to match your specific API requirements and response format:

# pagination.py
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response

class CustomPagination(PageNumberPagination):
    page_size = 10
    
    def get_paginated_response(self, data):
        return Response({
            'total_items': self.page.paginator.count,
            'total_pages': self.page.paginator.num_pages,
            'current_page': self.page.number,
            'next': self.get_next_link(),
            'previous': self.get_previous_link(),
            'data': data
        })

🧠 Test Your Knowledge

Which pagination is best for large, frequently changing datasets?