📄

【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

ログインするとコメントできます