pagination

package module
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 7, 2026 License: MIT Imports: 10 Imported by: 0

README

gin-pagination

gin-pagination is a small Go package for request pagination in gin. It goes beyond copying page and size into gin.Context by providing validated params and response metadata helpers.

  • Uses strongly typed Params instead of loose integer context keys.
  • Calculates offset and limit for database queries.
  • Validates broken configuration at startup.
  • Uses namespaced response headers by default: X-Pagination-*.
  • Supports custom error handling for API-specific error formats.
  • Provides a Meta builder for total count based API payloads.
  • Explicitly supports both 1-based and 0-based paging through BasePage.
  • Provides a separate offset pagination API with offset / limit.
  • Provides a separate cursor pagination API with after / before / limit.

This repository is intentionally organized as a flat root-level library package. Consumers import:

import "github.com/LimJiAn/gin-pagination"

There is no pkg/... subpath because this repository itself is the public package.

Scope

This package handles request pagination concerns only:

  • Parse pagination query parameters.
  • Validate request values.
  • Store typed params in gin.Context.
  • Build compact response metadata.

It does not execute database queries, build SQL, own repository logic, or apply sorting. Pass the parsed params to your repository, ORM, SQL builder, or generated query layer.

Sorting is intentionally out of scope because sort fields should be validated against an application-specific allowlist before they are applied to SQL or ORM queries.

Install

go get github.com/LimJiAn/gin-pagination

Quick Start

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
	pagination "github.com/LimJiAn/gin-pagination"
)

func main() {
	r := gin.Default()

	pager := pagination.Must(
		pagination.WithDefaultSize(25),
		pagination.WithMaxSize(100),
	)

	r.GET("/users", pager.Middleware(), func(c *gin.Context) {
		params := pager.MustFromContext(c)

		users := []gin.H{
			{"id": 1, "name": "alice"},
		}
		totalUsers := int64(57)

		meta := pager.Meta(totalUsers, params)
		c.JSON(http.StatusOK, gin.H{
			"data": users,
			"meta": meta,
		})
	})

	_ = r.Run(":8080")
}

Offset Pagination

For admin tables, exports, and APIs that prefer direct database offsets, use the offset pager instead of page/size.

offsetPager := pagination.MustOffset(
	pagination.WithOffsetDefaultLimit(20),
	pagination.WithOffsetMaxLimit(100),
)

r.GET("/users", offsetPager.Middleware(), func(c *gin.Context) {
	params := offsetPager.MustFromContext(c)

	rows, total, err := repository.ListUsers(c.Request.Context(), params.Offset, params.Limit)
	if err != nil {
		return
	}

	meta := offsetPager.Meta(total, params)
	c.JSON(http.StatusOK, gin.H{
		"data": rows,
		"meta": meta,
	})
})

The default query shape is:

GET /users?offset=20&limit=10

Cursor Pagination

For feeds, timelines, and append-only datasets, use the cursor pager instead of page/size.

cursorPager := pagination.MustCursor(
	pagination.WithCursorDefaultLimit(20),
	pagination.WithCursorMaxLimit(100),
)

r.GET("/feed", cursorPager.Middleware(), func(c *gin.Context) {
	params := cursorPager.MustFromContext(c)

	// A common pattern is to fetch limit + 1 rows, then trim back to limit.
	rows := []gin.H{
		{"id": "post_101"},
		{"id": "post_102"},
		{"id": "post_103"},
	}
	window := pagination.TrimCursorWindow(rows, params.Limit)

	info := pagination.CursorPageInfo{}
	if len(window.Items) > 0 {
		info = pagination.BuildCursorPageInfo(
			params,
			window.Items[0]["id"].(string),
			window.Items[len(window.Items)-1]["id"].(string),
			window.HasMore,
		)
	}

	meta := cursorPager.Meta(params, info)

	c.JSON(http.StatusOK, gin.H{
		"data": window.Items,
		"meta": meta,
	})
})

The default query shape is:

GET /feed?after=post_100&limit=20
GET /feed?before=post_200&limit=20

For database queries, use params.FetchLimit() in most cases.

rows, err := repository.ListFeed(c.Request.Context(), params.FetchLimit(), params.After, params.Before)
if err != nil {
	return
}

window := pagination.TrimCursorWindow(rows, params.Limit)

If you need opaque cursors, use the built-in created_at + id helper.

cursor := pagination.NewTimeIDCursor(post.CreatedAt, post.ID)
token, err := cursor.Encode()
if err != nil {
	return
}

decoded, err := pagination.DecodeTimeIDCursor(token)
if err != nil {
	return
}

_ = decoded

BuildCursorPageInfo and CursorMeta expose startCursor and endCursor. Clients can use endCursor as the next after value, or startCursor as the next before value.

Manual Parse

If you do not want middleware, parse directly.

params, err := pager.Parse(c)
if err != nil {
	return
}

rows, total, err := repository.ListUsers(c.Request.Context(), params.Offset, params.Limit)
if err != nil {
	return
}

c.JSON(http.StatusOK, gin.H{
	"data": rows,
	"meta": pager.Meta(total, params),
})

Offset and cursor pagination can also be parsed directly.

offsetParams, err := pagination.ParseOffset(c)
if err != nil {
	return
}

cursorParams, err := pagination.ParseCursor(c)
if err != nil {
	return
}

_, _ = offsetParams, cursorParams

Response Examples

Page/size responses include the current page, page size, total count, total pages, and navigation flags.

{
  "data": [],
  "meta": {
    "page": 3,
    "size": 15,
    "totalItems": 47,
    "totalPages": 4,
    "hasPrev": true,
    "hasNext": true
  }
}

Offset/limit responses include the current offset, limit, total count, and navigation flags.

{
  "data": [],
  "meta": {
    "offset": 30,
    "limit": 15,
    "totalItems": 57,
    "hasPrev": true,
    "hasNext": true
  }
}

Cursor responses include the current limit, directional availability, and the start/end cursors for the current window.

{
  "data": [],
  "meta": {
    "limit": 10,
    "hasBefore": true,
    "hasAfter": true,
    "startCursor": "post_101",
    "endCursor": "post_110"
  }
}

Options

pager := pagination.Must(
	pagination.WithPageQuery("page"),
	pagination.WithSizeQuery("pageSize"),
	pagination.WithBasePage(1),
	pagination.WithDefaultPage(1),
	pagination.WithDefaultSize(20),
	pagination.WithMinSize(1),
	pagination.WithMaxSize(100),
	pagination.WithHeaderPrefix("X-Pagination-"),
	pagination.WithContextKey("pagination.params"),
)

Offset pagination has its own options.

offsetPager := pagination.MustOffset(
	pagination.WithOffsetQuery("offset"),
	pagination.WithOffsetLimitQuery("limit"),
	pagination.WithOffsetDefaultOffset(0),
	pagination.WithOffsetDefaultLimit(20),
	pagination.WithOffsetMinLimit(1),
	pagination.WithOffsetMaxLimit(100),
	pagination.WithOffsetHeaderPrefix("X-Offset-Pagination-"),
	pagination.WithOffsetContextKey("pagination.offset"),
)

Cursor pagination has its own options.

cursorPager := pagination.MustCursor(
	pagination.WithCursorAfterQuery("after"),
	pagination.WithCursorBeforeQuery("before"),
	pagination.WithCursorLimitQuery("limit"),
	pagination.WithCursorDefaultLimit(20),
	pagination.WithCursorMinLimit(1),
	pagination.WithCursorMaxLimit(100),
	pagination.WithCursorHeaderPrefix("X-Cursor-Pagination-"),
	pagination.WithCursorContextKey("pagination.cursor"),
)

Error Shape

The default middleware error response is:

{
  "error": {
    "code": "invalid_integer",
    "field": "page",
    "value": "abc",
    "message": "page must be a valid integer"
  }
}

Optional Helpers

If you want a conventional JSON envelope, use these helpers:

pagination.NewResponse(data, meta)
pagination.NewOffsetResponse(data, meta)
pagination.NewCursorResponse(data, meta)

These helpers are optional. The core library surface is the parser, params, and meta builders.

Notes

  • Blank values such as ?page=&size= are treated as defaults.
  • BasePage must be 0 or greater.
  • Duplicate page and size query keys are rejected at startup.
  • Offset overflow from large page * size combinations is rejected.
  • Offset pagination rejects negative offset values.
  • Cursor pagination does not accept after and before together.
  • Cursor helpers provide opaque tokens, (created_at, id) keyset support, and limit + 1 trimming.
  • For empty cursor results, pass CursorPageInfo{} to omit cursor fields.

Documentation

Overview

Package pagination provides page-based, offset-based, and cursor-based pagination helpers for Gin.

It parses pagination query parameters, validates request values, stores typed params in gin.Context, and builds compact response metadata for JSON APIs.

The package supports three pagination styles:

  • page/size pagination: ?page=1&size=20
  • offset/limit pagination: ?offset=20&limit=10
  • cursor pagination: ?after=cursor&limit=10 or ?before=cursor&limit=10

Page/size pagination is useful for traditional paged API responses. Offset pagination is useful for admin tables, exports, and APIs that expose database offsets directly. Cursor pagination is useful for feeds, timelines, and append-only datasets where stable forward/backward navigation matters.

The root package intentionally exposes all pagination styles from a single import path:

import "github.com/LimJiAn/gin-pagination"

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func CursorMiddleware

func CursorMiddleware(options ...CursorOption) gin.HandlerFunc

CursorMiddleware is a convenience helper for the default Gin middleware case.

func DefaultErrorHandler

func DefaultErrorHandler(c *gin.Context, err *RequestError)

DefaultErrorHandler writes a default JSON error body with status 400. Applications can replace it with WithErrorHandler to control their own response shape.

func Middleware

func Middleware(options ...Option) gin.HandlerFunc

Middleware is a convenience helper for the default Gin middleware case.

func OffsetMiddleware

func OffsetMiddleware(options ...OffsetOption) gin.HandlerFunc

OffsetMiddleware is a convenience helper for the default Gin middleware case.

Types

type Config

type Config struct {
	PageQuery      string
	SizeQuery      string
	BasePage       int
	DefaultPage    int
	DefaultSize    int
	MinSize        int
	MaxSize        int
	ContextKey     string
	HeaderPrefix   string
	IncludeHeaders bool
	ErrorHandler   ErrorHandler
}

Config controls how pagination is parsed and exposed.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the default pagination configuration.

func (Config) Validate

func (c Config) Validate() error

Validate reports invalid pagination configuration early at startup.

type CursorConfig

type CursorConfig struct {
	AfterQuery     string
	BeforeQuery    string
	LimitQuery     string
	DefaultLimit   int
	MinLimit       int
	MaxLimit       int
	ContextKey     string
	HeaderPrefix   string
	IncludeHeaders bool
	ErrorHandler   ErrorHandler
}

CursorConfig controls how cursor pagination is parsed and exposed.

func DefaultCursorConfig

func DefaultCursorConfig() CursorConfig

DefaultCursorConfig returns the default cursor pagination configuration.

func (CursorConfig) Validate

func (c CursorConfig) Validate() error

Validate reports invalid cursor pagination configuration early at startup.

type CursorDirection

type CursorDirection string

CursorDirection represents which cursor parameter shape the request used.

const (
	// CursorDirectionForward means the request paginates with an "after" cursor.
	CursorDirectionForward CursorDirection = "forward"
	// CursorDirectionBackward means the request paginates with a "before" cursor.
	CursorDirectionBackward CursorDirection = "backward"
)

type CursorMeta

type CursorMeta struct {
	Limit       int     `json:"limit"`
	HasBefore   bool    `json:"hasBefore"`
	HasAfter    bool    `json:"hasAfter"`
	StartCursor *string `json:"startCursor,omitempty"`
	EndCursor   *string `json:"endCursor,omitempty"`
}

CursorMeta describes cursor paging metadata for a collection response.

type CursorOption

type CursorOption func(*CursorConfig)

CursorOption mutates the cursor pagination config.

func WithCursorAfterQuery

func WithCursorAfterQuery(value string) CursorOption

WithCursorAfterQuery changes the after query parameter name.

func WithCursorBeforeQuery

func WithCursorBeforeQuery(value string) CursorOption

WithCursorBeforeQuery changes the before query parameter name.

func WithCursorContextKey

func WithCursorContextKey(value string) CursorOption

WithCursorContextKey changes the gin context key used to store CursorParams.

func WithCursorDefaultLimit

func WithCursorDefaultLimit(value int) CursorOption

WithCursorDefaultLimit changes the default cursor page size.

func WithCursorErrorHandler

func WithCursorErrorHandler(handler ErrorHandler) CursorOption

WithCursorErrorHandler changes how invalid cursor pagination requests are written.

func WithCursorHeaderPrefix

func WithCursorHeaderPrefix(value string) CursorOption

WithCursorHeaderPrefix changes the prefix used for cursor pagination headers.

func WithCursorHeaders

func WithCursorHeaders(enabled bool) CursorOption

WithCursorHeaders enables or disables cursor pagination response headers.

func WithCursorLimitQuery

func WithCursorLimitQuery(value string) CursorOption

WithCursorLimitQuery changes the limit query parameter name.

func WithCursorMaxLimit

func WithCursorMaxLimit(value int) CursorOption

WithCursorMaxLimit changes the maximum allowed limit.

func WithCursorMinLimit

func WithCursorMinLimit(value int) CursorOption

WithCursorMinLimit changes the minimum allowed limit.

func WithoutCursorHeaders

func WithoutCursorHeaders() CursorOption

WithoutCursorHeaders disables cursor pagination response headers.

type CursorPageInfo

type CursorPageInfo struct {
	StartCursor string
	EndCursor   string
	HasBefore   bool
	HasAfter    bool
}

CursorPageInfo is the caller-provided before/after navigation state for cursor responses.

func BuildCursorPageInfo

func BuildCursorPageInfo(params CursorParams, startCursor string, endCursor string, hasMore bool) CursorPageInfo

BuildCursorPageInfo derives before/after navigation flags from request direction and limit+1 results.

type CursorPager

type CursorPager struct {
	// contains filtered or unexported fields
}

CursorPager parses cursor pagination requests and can expose them through Gin middleware.

func MustCursor

func MustCursor(options ...CursorOption) *CursorPager

MustCursor creates a CursorPager and panics when the config is invalid.

func NewCursor

func NewCursor(options ...CursorOption) (*CursorPager, error)

NewCursor creates a CursorPager from the supplied options.

func (*CursorPager) Config

func (p *CursorPager) Config() CursorConfig

Config returns a copy of the CursorPager configuration.

func (*CursorPager) FromContext

func (p *CursorPager) FromContext(c *gin.Context) (CursorParams, bool)

FromContext returns cursor params previously stored by the middleware.

func (*CursorPager) Meta

func (p *CursorPager) Meta(params CursorParams, info CursorPageInfo) CursorMeta

Meta builds cursor paging metadata for the current window of data.

func (*CursorPager) Middleware

func (p *CursorPager) Middleware() gin.HandlerFunc

Middleware returns a Gin middleware that parses and stores cursor pagination params.

func (*CursorPager) MustFromContext

func (p *CursorPager) MustFromContext(c *gin.Context) CursorParams

MustFromContext returns stored cursor params or panics when they are missing.

func (*CursorPager) Parse

func (p *CursorPager) Parse(c *gin.Context) (CursorParams, error)

Parse reads the current request and returns validated cursor pagination params.

type CursorParams

type CursorParams struct {
	After     string          `json:"after,omitempty"`
	Before    string          `json:"before,omitempty"`
	Limit     int             `json:"limit"`
	Direction CursorDirection `json:"direction"`
}

CursorParams is the validated cursor pagination state for a request.

func ParseCursor

func ParseCursor(c *gin.Context, options ...CursorOption) (CursorParams, error)

ParseCursor is a convenience helper for one-off parsing without building a CursorPager.

Example
package main

import (
	"fmt"
	"net/http/httptest"

	pagination "github.com/LimJiAn/gin-pagination"
	"github.com/gin-gonic/gin"
)

func main() {
	gin.SetMode(gin.TestMode)

	pager := pagination.MustCursor(
		pagination.WithCursorDefaultLimit(20),
		pagination.WithCursorMaxLimit(100),
	)

	recorder := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(recorder)
	c.Request = httptest.NewRequest("GET", "/feed?after=post_100&limit=10", nil)

	params, err := pager.Parse(c)
	if err != nil {
		panic(err)
	}

	rows := []string{
		"post_101", "post_102", "post_103", "post_104", "post_105",
		"post_106", "post_107", "post_108", "post_109", "post_110",
		"post_111",
	}
	window := pagination.TrimCursorWindow(rows, params.Limit)
	meta := pager.Meta(params, pagination.BuildCursorPageInfo(
		params,
		window.Items[0],
		window.Items[len(window.Items)-1],
		window.HasMore,
	))

	fmt.Printf("direction=%s after=%s limit=%d endCursor=%s\n",
		params.Direction,
		params.After,
		params.Limit,
		*meta.EndCursor,
	)

}
Output:
direction=forward after=post_100 limit=10 endCursor=post_110

func (CursorParams) FetchLimit

func (p CursorParams) FetchLimit() int

FetchLimit returns the query limit to use for limit+1 cursor pagination.

func (CursorParams) HasAfter

func (p CursorParams) HasAfter() bool

HasAfter reports whether the request continues after a cursor.

func (CursorParams) HasBefore

func (p CursorParams) HasBefore() bool

HasBefore reports whether the request continues before a cursor.

type CursorResponse

type CursorResponse[T any] struct {
	Data T          `json:"data"`
	Meta CursorMeta `json:"meta"`
}

CursorResponse is an optional convenience envelope for cursor-paginated JSON APIs.

func NewCursorResponse

func NewCursorResponse[T any](data T, meta CursorMeta) CursorResponse[T]

NewCursorResponse wraps data with cursor pagination metadata.

type CursorWindow

type CursorWindow[T any] struct {
	Items   []T
	HasMore bool
}

CursorWindow is the result of trimming a limit+1 fetch back to the requested page size.

func TrimCursorWindow

func TrimCursorWindow[T any](items []T, limit int) CursorWindow[T]

TrimCursorWindow trims a limit+1 result set back to the requested page size.

type ErrorHandler

type ErrorHandler func(c *gin.Context, err *RequestError)

ErrorHandler handles invalid pagination requests inside middleware.

type Meta

type Meta struct {
	Page       int   `json:"page"`
	Size       int   `json:"size"`
	TotalItems int64 `json:"totalItems"`
	TotalPages int   `json:"totalPages"`
	HasPrev    bool  `json:"hasPrev"`
	HasNext    bool  `json:"hasNext"`
}

Meta describes paging metadata to return alongside a collection response.

type OffsetConfig

type OffsetConfig struct {
	OffsetQuery    string
	LimitQuery     string
	DefaultOffset  int
	DefaultLimit   int
	MinLimit       int
	MaxLimit       int
	ContextKey     string
	HeaderPrefix   string
	IncludeHeaders bool
	ErrorHandler   ErrorHandler
}

OffsetConfig controls how offset pagination is parsed and exposed.

func DefaultOffsetConfig

func DefaultOffsetConfig() OffsetConfig

DefaultOffsetConfig returns the default offset pagination configuration.

func (OffsetConfig) Validate

func (c OffsetConfig) Validate() error

Validate reports invalid offset pagination configuration early at startup.

type OffsetMeta

type OffsetMeta struct {
	Offset     int   `json:"offset"`
	Limit      int   `json:"limit"`
	TotalItems int64 `json:"totalItems"`
	HasPrev    bool  `json:"hasPrev"`
	HasNext    bool  `json:"hasNext"`
}

OffsetMeta describes offset paging metadata for a collection response.

type OffsetOption

type OffsetOption func(*OffsetConfig)

OffsetOption mutates the offset pagination config.

func WithOffsetContextKey

func WithOffsetContextKey(value string) OffsetOption

WithOffsetContextKey changes the gin context key used to store OffsetParams.

func WithOffsetDefaultLimit

func WithOffsetDefaultLimit(value int) OffsetOption

WithOffsetDefaultLimit changes the default limit.

func WithOffsetDefaultOffset

func WithOffsetDefaultOffset(value int) OffsetOption

WithOffsetDefaultOffset changes the default offset.

func WithOffsetErrorHandler

func WithOffsetErrorHandler(handler ErrorHandler) OffsetOption

WithOffsetErrorHandler changes how invalid offset pagination requests are written.

func WithOffsetHeaderPrefix

func WithOffsetHeaderPrefix(value string) OffsetOption

WithOffsetHeaderPrefix changes the prefix used for offset pagination headers.

func WithOffsetHeaders

func WithOffsetHeaders(enabled bool) OffsetOption

WithOffsetHeaders enables or disables offset pagination response headers.

func WithOffsetLimitQuery

func WithOffsetLimitQuery(value string) OffsetOption

WithOffsetLimitQuery changes the limit query parameter name.

func WithOffsetMaxLimit

func WithOffsetMaxLimit(value int) OffsetOption

WithOffsetMaxLimit changes the maximum allowed limit.

func WithOffsetMinLimit

func WithOffsetMinLimit(value int) OffsetOption

WithOffsetMinLimit changes the minimum allowed limit.

func WithOffsetQuery

func WithOffsetQuery(value string) OffsetOption

WithOffsetQuery changes the offset query parameter name.

func WithoutOffsetHeaders

func WithoutOffsetHeaders() OffsetOption

WithoutOffsetHeaders disables offset pagination response headers.

type OffsetPager

type OffsetPager struct {
	// contains filtered or unexported fields
}

OffsetPager parses offset pagination requests and can expose them through Gin middleware.

func MustOffset

func MustOffset(options ...OffsetOption) *OffsetPager

MustOffset creates an OffsetPager and panics when the config is invalid.

func NewOffset

func NewOffset(options ...OffsetOption) (*OffsetPager, error)

NewOffset creates an OffsetPager from the supplied options.

func (*OffsetPager) Config

func (p *OffsetPager) Config() OffsetConfig

Config returns a copy of the OffsetPager configuration.

func (*OffsetPager) FromContext

func (p *OffsetPager) FromContext(c *gin.Context) (OffsetParams, bool)

FromContext returns offset params previously stored by the middleware.

func (*OffsetPager) Meta

func (p *OffsetPager) Meta(totalItems int64, params OffsetParams) OffsetMeta

Meta builds offset paging metadata for a known total item count.

func (*OffsetPager) Middleware

func (p *OffsetPager) Middleware() gin.HandlerFunc

Middleware returns a Gin middleware that parses and stores offset pagination params.

func (*OffsetPager) MustFromContext

func (p *OffsetPager) MustFromContext(c *gin.Context) OffsetParams

MustFromContext returns stored offset params or panics when they are missing.

func (*OffsetPager) Parse

func (p *OffsetPager) Parse(c *gin.Context) (OffsetParams, error)

Parse reads the current request and returns validated offset pagination params.

type OffsetParams

type OffsetParams struct {
	Offset int `json:"offset"`
	Limit  int `json:"limit"`
}

OffsetParams is the validated offset pagination state for a request.

func ParseOffset

func ParseOffset(c *gin.Context, options ...OffsetOption) (OffsetParams, error)

ParseOffset is a convenience helper for one-off parsing without building an OffsetPager.

Example
package main

import (
	"fmt"
	"net/http/httptest"

	pagination "github.com/LimJiAn/gin-pagination"
	"github.com/gin-gonic/gin"
)

func main() {
	gin.SetMode(gin.TestMode)

	pager := pagination.MustOffset(
		pagination.WithOffsetDefaultLimit(20),
		pagination.WithOffsetMaxLimit(100),
	)

	recorder := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(recorder)
	c.Request = httptest.NewRequest("GET", "/users?offset=30&limit=15", nil)

	params, err := pager.Parse(c)
	if err != nil {
		panic(err)
	}

	meta := pager.Meta(57, params)
	fmt.Printf("offset=%d limit=%d totalItems=%d hasNext=%t\n",
		params.Offset,
		params.Limit,
		meta.TotalItems,
		meta.HasNext,
	)

}
Output:
offset=30 limit=15 totalItems=57 hasNext=true

type OffsetResponse

type OffsetResponse[T any] struct {
	Data T          `json:"data"`
	Meta OffsetMeta `json:"meta"`
}

OffsetResponse is an optional convenience envelope for offset-paginated JSON APIs.

func NewOffsetResponse

func NewOffsetResponse[T any](data T, meta OffsetMeta) OffsetResponse[T]

NewOffsetResponse wraps data with offset pagination metadata.

type OpaqueCursorCodec

type OpaqueCursorCodec struct {
	// contains filtered or unexported fields
}

OpaqueCursorCodec encodes cursor values into opaque transport-safe tokens.

func NewOpaqueCursorCodec

func NewOpaqueCursorCodec() OpaqueCursorCodec

NewOpaqueCursorCodec returns the default URL-safe opaque cursor codec.

func (OpaqueCursorCodec) Decode

func (c OpaqueCursorCodec) Decode(token string) ([]string, error)

Decode turns an opaque token back into its cursor values.

func (OpaqueCursorCodec) DecodeTimeIDCursor

func (c OpaqueCursorCodec) DecodeTimeIDCursor(token string) (TimeIDCursor, error)

DecodeTimeIDCursor turns an opaque token into a keyset cursor.

func (OpaqueCursorCodec) Encode

func (c OpaqueCursorCodec) Encode(values ...string) (string, error)

Encode turns one or more cursor values into an opaque token.

func (OpaqueCursorCodec) EncodeTimeIDCursor

func (c OpaqueCursorCodec) EncodeTimeIDCursor(cursor TimeIDCursor) (string, error)

EncodeTimeIDCursor turns a keyset cursor into an opaque token.

type Option

type Option func(*Config)

Option mutates the pagination config.

func WithBasePage

func WithBasePage(value int) Option

WithBasePage changes the first valid page number.

func WithContextKey

func WithContextKey(value string) Option

WithContextKey changes the gin context key used to store Params.

func WithDefaultPage

func WithDefaultPage(value int) Option

WithDefaultPage changes the default page number.

func WithDefaultSize

func WithDefaultSize(value int) Option

WithDefaultSize changes the default page size.

func WithErrorHandler

func WithErrorHandler(handler ErrorHandler) Option

WithErrorHandler changes how invalid requests are written.

func WithHeaderPrefix

func WithHeaderPrefix(value string) Option

WithHeaderPrefix changes the prefix used for pagination headers.

func WithHeaders

func WithHeaders(enabled bool) Option

WithHeaders enables or disables pagination response headers.

func WithMaxSize

func WithMaxSize(value int) Option

WithMaxSize changes the maximum allowed page size.

func WithMinSize

func WithMinSize(value int) Option

WithMinSize changes the minimum allowed page size.

func WithPageQuery

func WithPageQuery(value string) Option

WithPageQuery changes the page query parameter name.

func WithSizeQuery

func WithSizeQuery(value string) Option

WithSizeQuery changes the size query parameter name.

func WithoutHeaders

func WithoutHeaders() Option

WithoutHeaders disables pagination response headers.

type Pager

type Pager struct {
	// contains filtered or unexported fields
}

Pager parses pagination requests and can expose them through Gin middleware.

func Must

func Must(options ...Option) *Pager

Must creates a Pager and panics when the config is invalid.

func New

func New(options ...Option) (*Pager, error)

New creates a Pager from the supplied options.

func (*Pager) Config

func (p *Pager) Config() Config

Config returns a copy of the Pager configuration.

func (*Pager) FromContext

func (p *Pager) FromContext(c *gin.Context) (Params, bool)

FromContext returns params previously stored by the middleware.

func (*Pager) Meta

func (p *Pager) Meta(totalItems int64, params Params) Meta

Meta builds paging metadata for a known total item count.

func (*Pager) Middleware

func (p *Pager) Middleware() gin.HandlerFunc

Middleware returns a Gin middleware that parses and stores pagination params.

func (*Pager) MustFromContext

func (p *Pager) MustFromContext(c *gin.Context) Params

MustFromContext returns stored params or panics when they are missing.

func (*Pager) Parse

func (p *Pager) Parse(c *gin.Context) (Params, error)

Parse reads the current request and returns validated pagination params.

type Params

type Params struct {
	Page   int `json:"page"`
	Size   int `json:"size"`
	Offset int `json:"offset"`
	Limit  int `json:"limit"`
}

Params is the validated pagination state for a request.

func Parse

func Parse(c *gin.Context, options ...Option) (Params, error)

Parse is a convenience helper for one-off parsing without building a Pager.

Example
package main

import (
	"fmt"
	"net/http/httptest"

	pagination "github.com/LimJiAn/gin-pagination"
	"github.com/gin-gonic/gin"
)

func main() {
	gin.SetMode(gin.TestMode)

	pager := pagination.Must(
		pagination.WithDefaultSize(20),
		pagination.WithMaxSize(100),
	)

	recorder := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(recorder)
	c.Request = httptest.NewRequest("GET", "/users?page=3&size=15", nil)

	params, err := pager.Parse(c)
	if err != nil {
		panic(err)
	}

	meta := pager.Meta(47, params)
	fmt.Printf("page=%d size=%d offset=%d limit=%d totalPages=%d hasNext=%t\n",
		params.Page,
		params.Size,
		params.Offset,
		params.Limit,
		meta.TotalPages,
		meta.HasNext,
	)

}
Output:
page=3 size=15 offset=30 limit=15 totalPages=4 hasNext=true

type RequestError

type RequestError struct {
	Code    string `json:"code"`
	Field   string `json:"field,omitempty"`
	Value   string `json:"value,omitempty"`
	Message string `json:"message"`
}

RequestError describes a client-facing pagination parse error.

func (*RequestError) Error

func (e *RequestError) Error() string

type Response

type Response[T any] struct {
	Data T    `json:"data"`
	Meta Meta `json:"meta"`
}

Response is an optional convenience envelope for paginated JSON APIs.

func NewResponse

func NewResponse[T any](data T, meta Meta) Response[T]

NewResponse wraps data with pagination metadata.

type TimeIDCursor

type TimeIDCursor struct {
	CreatedAt time.Time `json:"createdAt"`
	ID        string    `json:"id"`
}

TimeIDCursor is a stable keyset cursor for (created_at, id) ordering.

Example
package main

import (
	"fmt"
	"time"

	pagination "github.com/LimJiAn/gin-pagination"
)

func main() {
	cursor := pagination.NewTimeIDCursor(
		time.Date(2026, time.April, 27, 12, 30, 0, 0, time.UTC),
		"post_101",
	)

	token, err := cursor.Encode()
	if err != nil {
		panic(err)
	}

	decoded, err := pagination.DecodeTimeIDCursor(token)
	if err != nil {
		panic(err)
	}

	fmt.Printf("createdAt=%s id=%s\n",
		decoded.CreatedAt.Format(time.RFC3339),
		decoded.ID,
	)

}
Output:
createdAt=2026-04-27T12:30:00Z id=post_101

func DecodeTimeIDCursor

func DecodeTimeIDCursor(token string) (TimeIDCursor, error)

DecodeTimeIDCursor decodes an opaque token into a stable keyset cursor with the default codec.

func NewTimeIDCursor

func NewTimeIDCursor(createdAt time.Time, id string) TimeIDCursor

NewTimeIDCursor creates a normalized keyset cursor.

func (TimeIDCursor) After

func (c TimeIDCursor) After(other TimeIDCursor) bool

After reports whether the cursor sorts after another cursor.

func (TimeIDCursor) Before

func (c TimeIDCursor) Before(other TimeIDCursor) bool

Before reports whether the cursor sorts before another cursor.

func (TimeIDCursor) Compare

func (c TimeIDCursor) Compare(other TimeIDCursor) int

Compare compares two keyset cursors by (created_at, id).

func (TimeIDCursor) Encode

func (c TimeIDCursor) Encode() (string, error)

Encode turns the keyset cursor into an opaque token with the default codec.

func (TimeIDCursor) EncodeWith

func (c TimeIDCursor) EncodeWith(codec OpaqueCursorCodec) (string, error)

EncodeWith turns the keyset cursor into an opaque token with the supplied codec.

func (TimeIDCursor) Validate

func (c TimeIDCursor) Validate() error

Validate reports invalid keyset cursor values.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL