Skip to content

Built-in Paginators#

2024 iThome Ironman Contest

This is the 24th article in the Django Ninja tutorial series.

The pagination (pagination) feature is quite important even in small projects with small datasets.

Without pagination, APIs will still work—but performance will be affected, especially when data volume is large.

When an API returns a massive amount of data at once, it not only increases the server load but may also cause client-side processing to slow down, and even lead to timeout or out-of-memory issues.

Through pagination, we can avoid transmitting huge amounts of data at once, improve API performance, and enhance user experience.

This topic will be divided into two articles (Part 1 & Part 2) to introduce how to implement pagination in Django Ninja—from built-in paginators to custom pagination classes to satisfy different needs.

For all code changes in this article, please refer to this PR.

GitHub Example Project#

👉 Django-Ninja-Tutorial


The Importance of Pagination#

The core role of pagination is to split large amounts of data into small portions for transmission, giving a little at a time, thereby avoiding performance issues.

Specifically, pagination helps us to:

  1. Reduce Server Pressure: No need to return all data at once, only processing data for a single page.
  2. Increase Network Transmission Speed: Transmitting too much data increases network latency and the risk of packet loss; pagination effectively reduces transmission demands.
  3. Enhance User Experience: Clients can quickly fetch and display initial data without waiting for all data to finish transmitting. Meanwhile, pagination reduces the pressure on the client side to process massive amounts of data.

Therefore, regardless of project scale, implementing an efficient pagination strategy provides a notable help to API scalability and user experience.

Now that we understand the importance of pagination, let's start implementing it!


Our example API this time is—"Get Post List".

Since the database in our project already contains more than 60 post records, it is very suitable for demonstration.

If you do not have this data, see "Mid-Break and Preparation" at the end of Article 12: Request (Part 4) Request Body and Schema Introduction.

In this article, we will use Django Ninja's built-in PageNumberPagination paginator to implement simple and effective pagination. The custom part will be left to the next article.

Django Ninja's Built-in Paginators#

In Django Ninja, pagination functionality can be achieved using the built-in paginate decorator combined with a paginator (i.e., pagination class).

Django Ninja provides two built-in paginators. For easy understanding, here is a plain explanation of them:

  1. LimitOffsetPagination: Performs pagination based on "where to start retrieving data" and "how many items to retrieve", suitable for very large datasets. For example: "Start from the 20th item, retrieve 10 items."
  2. PageNumberPagination: Performs pagination via page numbers. The user only needs to specify the page they want, for example: "Retrieve data for page 2." The amount of data per page can be configured by the developer.

Personally, I prefer using PageNumberPagination or customizing a similar version (which is the content of the next article).

However, the default paginator is LimitOffsetPagination, so when using the paginate decorator, you need to explicitly declare the first parameter. You will see what that means in a moment.

Before that, let's review the current state of the "Get Post List" API.

Current State of the API#

As shown below, because pagination has not yet been implemented, it will return all post data at once:

@router.get('/posts/', response=list[PostListResponse], ...)
def get_posts(
    request: HttpRequest,
    title: None | str = Query(None, min_length=2, max_length=10),
) -> QuerySet[Post]:
    """
    Get post list
    """
    posts = Post.objects.all()
    if title:
        posts = posts.filter(
            title__icontains=title).select_related('author')
    return posts

This works fine when there are few posts, but as data volume increases, performance will be affected.

Next, we will improve this issue using Django Ninja's built-in paginator.


Implementing Pagination With PageNumberPagination#

Using the built-in PageNumberPagination, we can easily add pagination to the API.

Just use the @paginate decorator on the view function and add parameters like this:

from ninja.pagination import PageNumberPagination, paginate
...

@router.get(...)
@paginate(PageNumberPagination, page_size=10)  # Pagination implementation
def get_posts(...) -> QuerySet[Post]:
    """
    Get post list
    """
    ...

I omitted most content, as you only need to focus on the pagination implementation here.

As mentioned earlier, the first parameter PageNumberPagination must be explicitly declared by you—which is not required if you are using LimitOffsetPagination.

The implementation effect is that each page now displays 10 posts. This quantity can be controlled via the page_size parameter.

Huh, what about page changing? Let's take a look at the source code of PageNumberPagination:

class PageNumberPagination(AsyncPaginationBase):
    class Input(Schema):
        page: int = Field(1, ge=1)

    ...

Input represents the query parameters of the request (discussed in detail in the next article). In other words, you can use the parameter page in the URL's query parameters to specify the "page number", achieving page changing effects.

Testing the Pagination Effect#

Call the API using ?page=2 as a query parameter and see the effect:

As expected!

The response indeed displays the content of page 2—starting from post ID 11, with 10 items in total.


Advantages and Limitations of Using Built-in Paginators#

Advantages#

  • Pagination requires only a Pagination Decorator and Built-In Paginator, and the number of records per page is easy to control.
  • Suitable for scenarios that do not require complex customization.

Limitations#

  • Lack of flexibility; for example, we cannot allow users to specify how many items to display per page.
  • The fields and format of the response are fixed—and a bit simple.

Summary#

Through Django Ninja's built-in paginators, we can quickly add pagination to APIs, instantly satisfying simple pagination requirements.

However, built-in paginators fall short in control flexibility and cannot construct customized responses. When pagination requirements become complex, it feels a bit lacking.

At this point, a custom paginator would be a better solution.

In the next article, we will explore how to customize pagination classes in Django Ninja to satisfy these advanced requirements.