0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rails × GraphQL でAPI開発|ページネーションを実装する

Posted at

概要

前回の記事に続き、Rails × GraphQL でページネーションを実装していきます。

本記事では、GraphQLにおける2つの主要なページネーション方式について解説し、Rails環境での実装方法を紹介します。

ページネーションについて

ページネーションには大きく2つの方式があります。
各方式の詳細と、GraphQLでの実装パターンをみていきましょう🙋‍♂️

カーソルページネーション

カーソルページネーションは、GraphQLの仕様を策定したRelayが推奨する方式です。
各アイテムに対して一意の「カーソル」を発行し、そのカーソルを基準にして「次の〇件」「前の〇件」という形でデータを取得します。

ChatGPT Image 2025年4月23日 22_32_01 (1).png

特徴

  • データの追加・削除が発生しても、カーソルを基準にするため結果が一貫している
  • 大量データでも効率的にページング可能
  • Relay仕様に準拠しており、GraphQLのエコシステムと親和性が高い

ユースケース

  • 無限スクロール
  • SNSのタイムライン
  • 大規模なデータセット

オフセットページネーション

オフセットページネーションは、従来のWebアプリケーションでよく使われる方式です。
「何件目から」「何件取得するか」を指定してデータを取得します。

ChatGPT Image 2025年4月23日 22_20_10 (1).png

特徴

  • 実装・理解が容易
  • 「〇ページ目」という概念があり、従来のUIと相性が良い
  • 任意のページに直接ジャンプ可能

ユースケース

  • 検索結果
  • ページ番号ナビゲーションが必要なケース

実装

ページネーションの方式について理解したところで実装していきましょう🙋

カーソルページネーション

カーソルページネーションの実装は比較的簡単です。
graphql-rubyでは、connection_typeを指定するだけで自動的にRelayスタイルのページネーションが有効になります。

1. クエリの実装

module Types
  class QueryType < Types::BaseObject

    # -- 追加 --
    field :users, Types::UserType.connection_type, null: false

    def users
      User.all
    end
    # -- ここまで --
  end
end

この実装により、以下の引数が使えるようになります。

  • first/last:⠀⠀取得するアイテム数
  • after/before: 指定カーソル以降/以前のアイテムを取得

2. クエリ実行

実装が完了したところでクエリを実行していきましょう!

{
  users(first: 5){
    edges{
      cursor
      node{
        id
        name
      }
    }
    pageInfo{
      endCursor
      hasNextPage
      startCursor
      hasPreviousPage
    }
  }
}

🔍 解説

  • first: 5 により、最初の5件のユーザーデータを取得
  • カーソル情報と、各ユーザー(node) の IDname を取得
  • ページ情報(次/前のページがあるか、先頭/末尾のカーソル)を取得

Response

レスポンスデータから以下のことがわかります。

  • カーソルは各レコードの IDBase64 エンコードした値
  • 次ページを取得するには after: "Mw" を指定する
{
  "data": {
    "users": {
      "edges": [
        {
          "cursor": "MQ",
          "node": {
            "id": "2",
            "name": "matsumoto"
          }
        },
        {
          "cursor": "Mg",
          "node": {
            "id": "3",
            "name": "honda"
          }
        },
        {
          "cursor": "Mw",
          "node": {
            "id": "4",
            "name": "sato"
          }
        }
      ],
      "pageInfo": {
        "endCursor": "Mw",
        "hasNextPage": true,
        "startCursor": "MQ",
        "hasPreviousPage": false
      }
    }
  }
}

オフセットページネーション

オフセットページネーションはgraphql-rubyで標準サポートされていませんが、カスタムタイプを作成することで実装できます。

ページネーションの Gem である kaminari と組み合わせた例を紹介します。

1. ページネーション型の作成

まず、ページネーション関連の情報を格納する型を定義します。

module Types
  class PaginationType < Types::BaseObject
    field :total_count, Integer, null: true
    field :limit_value, Integer, null: true
    field :total_pages, Integer, null: true
    field :current_page, Integer, null: true
  end
end

この型には以下の情報が含まれます。

  • total_count: 全レコード数
  • limit_value: 1ページあたりの表示件数
  • total_pages: 全ページ数
  • current_page: 現在のページ番号

2. コンテナ型の作成

次に、ページネーション情報と実際のデータを格納するコンテナ型を作成します。

module Types
  class UsersType < Types::BaseObject
    field :pagination, PaginationType, null: true
    field :posts, [PostType], null: true
  end
end
  • データ本体(posts)とメタ情報(pagination)を分離

3. クエリの実装

最後に、query_type.rbにオフセットページネーションを使用するフィールドを追加します。

# frozen_string_literal: true

module Types
  class QueryType < Types::BaseObject
    # --- 追加 ---
    field :users, Types::UsersType, null: false do
      argument :page, Integer, required: false, default_value: 1
      argument :per_page, Integer, required: false, default_value: 15
    end

    def users(page:, per_page:)
      users = User.all.page(page).per(per_page)
      {
        users:,
        pagination: pagination(users)
      }
    end

    private
      def pagination(result)
        {
          total_count: result.total_count,
          limit_value: result.limit_value,
          total_pages: result.total_pages,
          current_page: result.current_page
        }
      end
    # --- ここまで ---
  end
end
  • 引数にpageper_pageを定義(デフォルト値も設定)
  • Kaminariの.page().per()メソッドを使用
  • レスポンスはuserspaginationの複合構造

4. クエリ実行

{
  users(page: 1, perPage: 3) {
    users {
      id
      name
    }
    pagination {
      totalCount
      currentPage
      totalPages
      limitValue
    }
  }
}
  • 1ページ目のデータを取得(3件ずつ)
  • ユーザーの IDname を取得

まとめ

睡魔に襲われながら頑張って書きました🙆‍♂️
寝ぼけているかもしれないので、Typo や 不足等あればコメントください...!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?