Easy API Pagination in Flask

2

Casey

July 22, 2021
Web Dev

In this post I will show you how to set up API pagination in Flask, thanks primarily to Flask-SqlAlchemy’s paginate method.

Our goal is to use a URL like this: /v1/magazines?page=1&per-page=50 to generate JSON results like this:

{
    "results": [
        {
            "Programming Today",
            "publisher": "Harper Collins"
        },
        {
            "title": "Mountain Biking",
            "publisher": "Outdoors Limited"
        }
    ],
    "pagination": {
        "count": 2,
        "page": 1,
        "per_page": 50,
        "pages": 1
    }
}

Let’s get straight to the code and write a basic example.

@app.route("/magazines")
def magazines():
    # process query parameters
    page = request.args.get("page", 1, type=int)
    per_page = request.args.get("per-page", 100, type=int)

    # query
    magazines = Magazine.query.paginate(page, per_page)

    # combine results with pagination
    results = {
        "results": [{"title": m.title, "publisher": m.publisher} for m in magazines.items],
        "pagination": {
            "count": magazines.total,
            "page": page,
            "per_page": per_page,
            "pages": magazines.pages,
        },
    }
    return jsonify(results)

That’s it! The paginate method on our sqlalchemy query is giving us a lot of flexibility with very little code, returning a limited result set, displaying total records available, while calculating remaining pages based on our per_page setting.

Structuring our API this way allows us to maintain performance by returning small chunks of data at a time, while allowing systems to page through all of the records if needed.

The pagination format in JSON is a simplified version of the White House API standards. While the White House prefers offset and limit for pagination, I think page and per_page is more intuitive.

Here are some ways we can improve this example:

  • Validate per_page parameter and set limits so someone cannot enter a high number (for example, above 100)
  • Move our Magazine object into its own schema using marshmallow
  • Add link headers which will allow some automated tools to page through results using headers

I’ve gone ahead and made these improvements. You can see a full working GitHub gist here.

Casey