DEV Community

Cover image for Gorm Pagination With Ease
Sheena Zien
Sheena Zien

Posted on

Gorm Pagination With Ease

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.

type PaginaterResolver struct {
    PaginatorInfo *PaginatorInfo `json:"paginatorInfo"`
    Data          interface{}    `json:"data"`
    model         interface{}
    request       Request
    stmt          *gorm.DB
}
Enter fullscreen mode Exit fullscreen mode

Next, we create the PaginatorInfo struct to display the pagination details.

type PaginatorInfo struct {
    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"`
}
Enter fullscreen mode Exit fullscreen mode

Then, we create the Request struct for pagination query parameters.

type Request struct {
    Offset   int
    Page     int
    PageSize int
}
Enter fullscreen mode Exit fullscreen mode

2. Create the Setter Methods

We'll add setter methods to set up the statement, model, and request.

func (s *PaginaterResolver) Stmt(stmt *gorm.DB) *PaginaterResolver {
    s.stmt = stmt

    return s
}

func (s *PaginaterResolver) Model(model interface{}) *PaginaterResolver {
    s.model = model

    return s
}
Enter fullscreen mode Exit fullscreen mode

For this example, we use GraphQL's ResolveParams to set the request parameters.

func (s *PaginaterResolver) Request(p graphql.ResolveParams) *PaginaterResolver {
    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
}

Enter fullscreen mode Exit fullscreen mode

You can customize this to accept different request types.

type RequestParams struct {
    Page     int
    PageSize int
}
func (s *PaginaterResolver) Request(p RequestParams) *PaginaterResolver {
    // Set request based on RequestParams
    return s
}
Enter fullscreen mode Exit fullscreen mode

🎉 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.

func (s *PaginaterResolver) Paginate() (PaginaterResolver, error) {
    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
}
Enter fullscreen mode Exit fullscreen mode
func (s *PaginaterResolver) Paging() func(db *gorm.DB) *gorm.DB {
    return func(db *gorm.DB) *gorm.DB {
        return db.Offset(s.request.Offset).Limit(s.request.PageSize)
    }
}
Enter fullscreen mode Exit fullscreen mode

🔥 Now, we can use this pagination code in our GORM queries. Here's an example:

paginaterResolver := new(scopes.PaginaterResolver)
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()
Enter fullscreen mode Exit fullscreen mode

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
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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! 🎉

Top comments (0)