Recently I’ve been struggling with pagination on Gorm, indeed gorm has an example that you need for this case, but it’s a limit and offset query, the case I faced is to return the pagination information, Like Count, CurrentPage, FirstItem, HasMorePages, LastItem, LastPage, PerPage, and Total
So after a ton of browsing this case on the internet, I decided to create it.
1. Create The Struct
First, we need to define the necessary structs for pagination. We’ll start by creating a resolver struct to hold all the required pagination information.
PaginatorInfo *PaginatorInfo `json:”paginatorInfo”`
Data interface{} `json:”data”`
model interface{}
request Request
stmt *gorm.DB
}
Next, we create the PaginatorInfo struct to display the pagination details.
Count int `json:”count”`
CurrentPage int `json:”currentPage”`
FirstItem int `json:”firstItem”`
HasMorePages bool `json:”hasMorePages”`
LastItem int `json:”lastItem”`
LastPage int `json:”lastPage”`
PerPage int `json:”perPage”`
Total int `json:”total”`
}
Then, we create the Request struct for pagination query parameters.
Offset int
Page int
PageSize int
}
2. Create the Setter Methods
We’ll add setter methods to set up the statement, model, and request.
s.stmt = stmt
return s
}
func (s *PaginaterResolver) Model(model interface{}) *PaginaterResolver {
s.model = model
return s
}
For this example, we use GraphQL’s ResolveParams to set the request parameters.
var page = 1
if p.Args[“page”] != nil {
page = p.Args[“page”].(int)
}
var pageSize = 10
if p.Args[“page_size”] != nil {
pageSize = p.Args[“page_size”].(int)
}
switch {
case pageSize > 100:
pageSize = 100
case pageSize <= 0:
pageSize = 10
}
offset := (page – 1) * pageSize
s.request = Request{Offset: offset, Page: page, PageSize: pageSize}
return s
}
You can customize this to accept different request types.
Page int
PageSize int
}
func (s *PaginaterResolver) Request(p RequestParams) *PaginaterResolver {
// Set request based on RequestParams
return s
}
🎉 We’ve set up our initial requirements!
3. Create the Main Methods for Pagination
We need two main methods: Paginate() to fill the PaginatorInfo struct, and Paging() to apply the scope for pagination.
var totalCount int64
s.stmt.Model(s.model).Count(&totalCount)
limit := s.request.PageSize
page := s.request.Page
offset := s.request.Offset
lastPage := int((totalCount + int64(limit) – 1) / int64(limit))
result := s.stmt.Scopes(s.Paging()).Find(s.model)
if result.RowsAffected == 0 {
config.LOG.Println(“No data found”)
return PaginaterResolver{Data: []interface{}{}}, nil
}
paginatorInfo := &PaginatorInfo{
Count: int(result.RowsAffected),
CurrentPage: page,
FirstItem: offset + 1,
HasMorePages: page < lastPage,
LastItem: offset + int(result.RowsAffected),
LastPage: lastPage,
PerPage: limit,
Total: int(totalCount),
}
s.PaginatorInfo = paginatorInfo
s.Data = s.model
return *s, nil
}
return func(db *gorm.DB) *gorm.DB {
return db.Offset(s.request.Offset).Limit(s.request.PageSize)
}
}
🔥 Now, we can use this pagination code in our GORM queries. Here’s an example:
query := config.DB.Scopes(
post.GetPage,
post.GetOnlyPublish,
scopes.Order(orderBy[0]),
).Preload(clause.Associations).Find(&posts)
if query.Error != nil {
return scopes.PaginaterResolver{}, query.Error
}
paginater, err := paginaterResolver.
Request(p).
Stmt(query).
Model(&posts).
Paginate()
Below is the result.
“data”: {
“paymentMethods”: {
“data”: [
{
“id”: 7,
“slug”: “payment-method-2”,
“type”: “paymentmethod”
},
// More items…
],
“paginatorInfo”: {
“count”: 10,
“currentPage”: 1,
“firstItem”: 1,
“hasMorePages”: true,
“lastItem”: 10,
“lastPage”: 2,
“perPage”: 10,
“total”: 16
}
}
}
}
Conclusion
Implementing pagination in GORM can be challenging, but by creating custom structs and methods, we can effectively manage pagination information. This approach ensures that all necessary pagination details are included in our API responses, making it easier to work with large datasets.
Feel free to customize and extend this implementation to suit your specific needs. Happy coding! 🎉