Gorm is the most used ORM package in Go, but, despite that, it is lacking some "basic" functionality.
One of those lacking features is Pagination.
Pagination is an essential feature for managing large datasets in web applications. It is an approach to limit and display part of the total data in a database, so then not all the table needs to be retrieved in one “go”.
While Gorm provides documentation on how to use pagination using scopes, it leaves room for improvement in flexibility and usability.
Here, I am going to explain an alternative to scopes for pagination, using Gorm’s Clauses.
Pagination Using Scopes
Gorm’s documentation introduces Scopes as a way to re use common code. In the documentation example we can see that they define a pagination scope function similar to this:
func Paginate(page, pageSize int) func(db *gorm.DB) *gorm.DB {
return func (db *gorm.DB) *gorm.DB {
// validate page and pageSize
...
offset := (page - 1) * pageSize
return db.Offset(offset).Limit(pageSize)
}
}
page := 0
pageSize := 10
db.Scopes(Paginate(page, pageSize)).Find(&users)
So, in order to apply pagination, we need to use the code
db.Scopes(Paginate(page, pageSize))…
Pagination Using Clauses
A different and, arguably, more elegant approach is to use Gorm Clauses, this approach is a bit different to using Scopes, since Clauses are on charge of modifying the database queries, in particular, the WHERE clause.
Let’s start by defining the pagination struct
type Pagination struct {
page int
pageSize int
}
func (p *Pagination) GetPage() int {
return p.page
}
func (p *Pagination) GetPageSize() int {
return p.pageSize
}
And then, let’s implement the two interfaces needed to use this struct in a gorm clause function:
clause.Expression
gorm.StatementModifier
So the struct looks like this:
func (p *Pagination) ModifyStatement(stm *gorm.Statement) {
// We modify statement to add the pagination
db := stm.DB
stm.DB.Limit(p.size).Offset((p.page - 1) * p.pageSize)
}
func (p *Pagination) Build(_ clause.Builder) {
// The Build method is left empty since pagination does not require any additional SQL clauses.
}
After that, you can use pagination as follow:
pagination := Pagination{
page: 0,
pageSize: 10,
}
db.Clauses(&pagination).Find(&users)
To make this approach reusable and enhance its capabilities, I developed PaGorminator — a library that uses this feature for pagination, while adding other features like unpaged requests and automatic metadata population like total number of pages, and total count.
To use it, do as follow:
// Add plugin
_ = db.Use(pagorminator.PaGormMinator{})
...
pageRequest, _ := pagorminator.PageRequest(0, 10)
db.Clauses(pageRequest).Find(&users)
// this will apply pagination, and also populate pageRequest with:
// - the total number of pages
// - total count