📄

【Go言語】ライブラリを使わないシンプルなページネーション

2023/01/10に公開

Go言語でREST APIを実装するにあたり、ページネーション機能を導入することに。

今回のAPIではORM系のライブラリは利用せず、生SQLでデータを取得する方法を採用しています。

そのため、やりたいことは取得したCustomerOrderといった構造体のスライスを指定されたページ番号1ページあたりのデータ数に応じて「データを分割し」、かつ「ページのmeta情報を付与」するシンプルな内容です。

同様の事例はあるだろうと期待して「golang ページネーション」とググってみたが、ヒットするのは、

がほとんどでした。。。GORMはそもそも利用していないので対象外。

pagination-goを利用しようかと考えましたが、メンテナンスされてないようですし、必要最低限の機能だけあれば十分なので自作してみることにしました。

必要な機能

要件として下記のリクエスト・レスポンスを満たすものを実装します。

リクエスト

  • page:取得するページ番号
  • perPage:1ページごとのデータ件数

レスポンス

  • perPage : 1ページごとのデータ件数
  • currentPage : 現在のページ番号
  • lastPage : 最後のページ番号
  • data : 該当ページのデータ(オブジェクトに応じて名前が変わる)

実装

 ▸ tree
.
├── go.mod
├── go.sum
├── helper
│   └── pagination.go
├── main.go
└── model
    └── customer.go
main.go
package main

import (
	"encoding/json"
	"fmt"

	"github.com/jaswdr/faker"

	"github.com/yusukeito58/go-pagination/helper"
	"github.com/yusukeito58/go-pagination/model"
)

type ResponseCustomers struct {
	Customers   []*model.Customer `json:"customers"`
	CurrentPage int               `json:"currentPage"`
	LastPage    int               `json:"lastPage"`
	PerPage     int               `json:"perPage"`
}

func main() {
	// 取得データ
	var customers []*model.Customer
	p := faker.New()
	for i := 1; i <= 50; i++ {
		customers = append(customers, &model.Customer{Id: i, Name: p.Person().Name()})
	}

	// リクエスト
	page := 2

	// 設定値
	perPage := 10

	// 結果
	jsonData, err := json.Marshal(adptCustomers(helper.Pagination(customers, page, perPage)))
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Printf("%s\n", jsonData)
}

func adptCustomers(customers []*model.Customer, currentPage int, lastPage int, perPage int) ResponseCustomers {
	return ResponseCustomers{
		Customers:   customers,
		CurrentPage: currentPage,
		LastPage:    lastPage,
		PerPage:     perPage,
	}
}
model/customer.go
package model

type Customer struct {
	Id   int
	Name string
}
helper/pagination.go
package helper

import (
	"math"
)

func Pagination[T comparable](x []T, page int, perPage int) (data []T, currentPage int, lastPage int) {
	lastPage = int(math.Ceil(float64(len(x)) / float64(perPage)))
	currentPage = page

	// 指定ページが範囲外の場合は補正
	if page < 1 {
		page = 1
	} else if lastPage < page {
		page = lastPage
	}

	if page == lastPage {
		data = x[(page-1)*perPage:]
	} else {
		data = x[(page-1)*perPage : page*perPage]
	}

	return
}

動作確認

 ▸ go run main.go | jq
{
  "customers": [
    {
      "Id": 11,
      "Name": "Carolanne Gerlach"
    },
    {
      "Id": 12,
      "Name": "Earnest Kulas PhD"
    },
    {
      "Id": 13,
      "Name": "Heloise Carroll"
    },
    {
      "Id": 14,
      "Name": "Bobbie Wisoky"
    },
    {
      "Id": 15,
      "Name": "Giovanny Wyman"
    },
    {
      "Id": 16,
      "Name": "Rickie Schulist DVM"
    },
    {
      "Id": 17,
      "Name": "Stefan Rau"
    },
    {
      "Id": 18,
      "Name": "Bulah Johnston"
    },
    {
      "Id": 19,
      "Name": "Marie Fisher"
    },
    {
      "Id": 20,
      "Name": "Timmothy Schamberger"
    }
  ],
  "currentPage": 2,
  "lastPage": 5,
  "perPage": 10
}

総データ数50に対し、1ページあたり10データで2ページ目のデータを取得した結果です。

11〜20件目のデータが取得され、ページのmeta情報について正しいことが確認できました。

Discussion