DEV Community

Cover image for Pagination using Gorm scopes
Rafael Firmino
Rafael Firmino

Posted on

Pagination using Gorm scopes

Gorm is a fantastic ORM writen in Go for gophers.


Today I will show you how to create pagination using Gorm Scopes.

Scopes allow you to re-use commonly used logic, the shared logic needs to be defined as type

func(*gorm.DB) *gorm.DB 
Enter fullscreen mode Exit fullscreen mode

The first step is create a Pagination struct.


//pkg.pagination    
package pkg 
 
type Pagination struct {    
    Limit        int         `json:"limit,omitempty;query:limit"`   
    Page         int         `json:"page,omitempty;query:page"` 
    Sort         string      `json:"sort,omitempty;query:sort"` 
    TotalRows    int64       `json:"total_rows"`    
    TotalPages   int         `json:"total_pages"`   
    Rows         interface{} `json:"rows"`  
}   
 
func (p *Pagination) GetOffset() int {  
    return (p.GetPage() - 1) * p.GetLimit() 
}   
 
func (p *Pagination) GetLimit() int {   
    if p.Limit == 0 {   
        p.Limit = 10    
    }   
    return p.Limit  
}   
 
func (p *Pagination) GetPage() int {    
    if p.Page == 0 {    
        p.Page = 1  
    }   
    return p.Page   
}   
 
func (p *Pagination) GetSort() string { 
    if p.Sort == "" {   
        p.Sort = "Id desc"  
    }   
    return p.Sort   
}   
Enter fullscreen mode Exit fullscreen mode


Second step is create a Gorm Scope.

func paginate(value interface{}, pagination *pkg.Pagination, db *gorm.DB) func(db *gorm.DB) *gorm.DB {  
    var totalRows int64 
    db.Model(value).Count(&totalRows)   
 
    pagination.TotalRows = totalRows    
    totalPages := int(math.Ceil(float64(totalRows) / float64(pagination.Limit)))    
    pagination.TotalPages = totalPages  
 
    return func(db *gorm.DB) *gorm.DB { 
        return db.Offset(pagination.GetOffset()).Limit(pagination.GetLimit()).Order(pagination.GetSort())   
    }   
}   
Enter fullscreen mode Exit fullscreen mode


The third step is use Gorm scope in your repository.

type CategoryGorm struct {  
    db *gorm.DB 
}   
 
func (cg *CategoryGorm) List(pagination pkg.Pagination) (*pkg.Pagination, error) {  
    var categories []*Category  
 
    cg.db.Scopes(paginate(categories,&pagination, cg.db)).Find(&categories) 
    pagination.Rows = categories    

    return &pagination, nil 
}   
Enter fullscreen mode Exit fullscreen mode

Rest Example

I won't talk about how you could implement your handles or access the repository, maybe another time.


The most important thing you need is to return the pkg.pagination for you client.


In a rest application, if I send

http://localhost:3000/categories?limit=10&page=5

Then the output would be:

{   
  "limit": 10,  
  "page": 5,    
  "sort": "Id desc",    
  "total_rows": 41, 
  "total_pages": 5, 
  "rows": [ 
    {   
      "id": "0acd5600-51b9-42e9-9fd0-ff422a6de1d1", 
      "category": "example41"   
    }   
  ] 
}   
Enter fullscreen mode Exit fullscreen mode

Latest comments (4)

Collapse
 
mostafabinesh profile image
Mostafa

Thanks. Was helpful

Collapse
 
gonzalezlrjesus profile image
Jesus Gonzalez

great article!

Collapse
 
sixcolors profile image
Jason McNeil • Edited

If you use this on a rest API per the post, be sure to validate the sort string against a whitelist of approved fields.

Otherwise you get SQL injection right here:

return db.Offset(pagination.GetOffset()).Limit(pagination.GetLimit()).Order(pagination.GetSort())

gorm.io/docs/security.html#SQL-inj...

Collapse
 
stevebaros profile image
Steve Baros • Edited

Thank you this is good

for those using gin , you can dynamically pick the parameters like this

 page, _ := strconv.Atoi(context.Query("page"))
    if page <= 0 {
        page = 1
    }

    pageSize, _ := strconv.Atoi(context.Query("per_page"))
    sort := context.Query("sort")
    var sortWithDirection string
    if sort != "" {
        direction := context.Query("sortDesc")
        if direction != "" {
            if direction == "true" {
                sortWithDirection = sort + " desc"
            } else if direction == "false" {
                sortWithDirection = sort + " asc"
            }
        }

    }

    switch {
    case pageSize <= 0:
        pageSize = 10
    }
Enter fullscreen mode Exit fullscreen mode