If you don’t paginate GET LIST data in your API, most likely you will be giving your API consumers too much data, and adding too much load on your API.

Whenever a user can have more than ±50 records in a list, you must add pagination to your API. It’s not that hard!

Example API GET request that contains pagination parameters:

curl -X 'GET' 'http://localhost:3000/api/v1/posts?page=2'

Response (current page is 2):

  "pagination": {
    "prev_url": "/api/v1/posts?page=1",
    "next_url": "/api/v1/posts?page=3",
    "count": 4,
    "page": 2,
    "next": 3
  "data": [
    {"id": 1, "title": "first post"},
    {"id": 2, "title": "second post"}

Without pagination, our default API response would look like this:

  {"id": 1, "title": "first post"},
  {"id": 2, "title": "second post"}

To add pagination parameters, we will use Pagy and update our jbuilder file.

bundle add pagy

Enable pagy_metadata() method, rescue from Pagy::OverflowError:

# config/initializers/pagy.rb
require 'pagy/extras/metadata'
require 'pagy/extras/overflow'
Pagy::DEFAULT[:overflow] = :empty_page

Add pagination, include pagy_metadata(@pagy):

# app/controllers/api/v1/posts_controller.rb
  include Pagy::Backend
  def index
    items_per_page = 12
    user_posts = current_user.posts.all
    @pagy, @posts = pagy(user_posts, items: items_per_page)
    @pagination = pagy_metadata(@pagy)

Rendering most important pagy_metadata within the json response:

# app/views/api/v1/posts/index.json.jbuilder
json.pagination do
  json.extract! @pagination, :prev_url, :next_url, :count, :page, :next
# json.links do
#   json.prev @pagination[:prev_url]
#   json.next @pagination[:next_url]
# end
json.data do
  json.array! @posts, partial: "api/v1/posts/post", as: :post

Add pagination to your OpenAPI yaml file:

+      parameters:
+        - in: query
+          name: page
+          schema:
+            type: integer
+            minimum: 1
+          description: Page number

Now, if you have followed me so far, you can make paganated API requests in your Swagger UI:


That’s it!