{"id":13630227,"url":"https://github.com/morkid/paginate","last_synced_at":"2025-04-04T22:05:08.255Z","repository":{"id":46798619,"uuid":"321696969","full_name":"morkid/paginate","owner":"morkid","description":"paginate - Gorm Pagination Library","archived":false,"fork":false,"pushed_at":"2025-01-17T00:43:52.000Z","size":86,"stargazers_count":104,"open_issues_count":2,"forks_count":17,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-28T21:04:33.405Z","etag":null,"topics":["database","elasticsearch","fasthttp","golang","gorm","jquery-datatable","jquery-select2","paginate","paginate-gorm","pagination"],"latest_commit_sha":null,"homepage":"https://morkid.github.io/paginate/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/morkid.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-12-15T14:44:45.000Z","updated_at":"2025-02-15T14:37:47.000Z","dependencies_parsed_at":"2024-06-18T17:59:08.572Z","dependency_job_id":"dab9e7a1-a03d-4d55-9959-cacc5622c7a0","html_url":"https://github.com/morkid/paginate","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morkid%2Fpaginate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morkid%2Fpaginate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morkid%2Fpaginate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morkid%2Fpaginate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/morkid","download_url":"https://codeload.github.com/morkid/paginate/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247256110,"owners_count":20909240,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","elasticsearch","fasthttp","golang","gorm","jquery-datatable","jquery-select2","paginate","paginate-gorm","pagination"],"created_at":"2024-08-01T22:01:34.764Z","updated_at":"2025-04-04T22:05:08.237Z","avatar_url":"https://github.com/morkid.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# paginate - Gorm Pagination\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/morkid/paginate.svg)](https://pkg.go.dev/github.com/morkid/paginate)\n[![Github Actions](https://github.com/morkid/paginate/workflows/Go/badge.svg)](https://github.com/morkid/paginate/actions)\n[![Go Report Card](https://goreportcard.com/badge/github.com/morkid/paginate)](https://goreportcard.com/report/github.com/morkid/paginate)\n[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/morkid/paginate)](https://github.com/morkid/paginate/releases)\n\nSimple way to paginate [Gorm](https://github.com/go-gorm/gorm) result. **paginate** is compatible with [net/http](https://golang.org/pkg/net/http/) and [fasthttp](https://github.com/valyala/fasthttp). This library also supports many net/http or fasthttp based frameworks.\n\n## Table Of Contents\n- [Installation](#installation)\n- [Configuration](#configuration)\n- [Pagination Result](#pagination-result)\n- [Paginate using http request](#paginate-using-http-request)\n- [Example usage](#example-usage)\n  - [net/http](#nethttp-example)\n  - [Fasthttp](#fasthttp-example)\n  - [Mux Router](#mux-router-example)\n  - [Fiber](#fiber-example)\n  - [Echo](#echo-example)\n  - [Gin](#gin-example)\n  - [Martini](#martini-example)\n  - [Beego](#beego-example)\n  - [jQuery DataTable Integration](#jquery-datatable-integration)\n  - [jQuery Select2 Integration](#jquery-select2-integration)\n  - [Programmatically Pagination](#programmatically-pagination)\n- [Filter format](#filter-format)\n- [Customize default configuration](#customize-default-configuration)\n- [Override results](#override-results)\n- [Field Selector](#field-selector)\n- [Dynamic Field Selector](#dynamic-field-selector)\n- [Speed up response with cache](#speed-up-response-with-cache)\n  - [In Memory Cache](#in-memory-cache)\n  - [Disk Cache](#disk-cache)\n  - [Redis Cache](#redis-cache)\n  - [Elasticsearch Cache](#elasticsearch-cache)\n  - [Custom cache](#custom-cache)\n  - [Clean up cache](#clean-up-cache)\n- [Limitations](#limitations)\n- [License](#license)\n\n## Installation\n\n```bash\ngo get -u github.com/morkid/paginate\n```\n\n## Configuration\n\n```go\nvar db *gorm.DB = ...\nvar req *http.Request = ...\n// or\n// var req *fasthttp.Request\n\nstmt := db.Where(\"id \u003e ?\", 1).Model(\u0026Article{})\npg := paginate.New()\npage := pg.With(stmt).Request(req).Response(\u0026[]Article{})\n\nlog.Println(page.Total)\nlog.Println(page.Items)\nlog.Println(page.First)\nlog.Println(page.Last)\n\n```\nyou can customize config with `paginate.Config` struct.  \n```go\npg := paginate.New(\u0026paginate.Config{\n    DefaultSize: 50,\n})\n```\nsee more about [customize default configuration](#customize-default-configuration).\n\n## Pagination Result\n\n```js\n{\n    // the result items\n    \"items\": *[]any, \n    \n    // total results\n    // including next pages\n    \"total\": number,   \n\n    // Current page\n    // (provided by request parameter, eg: ?page=1)\n    // note: page is always start from 0\n    \"page\": number,\n    \n    // Current size\n    // (provided by request parameter, eg: ?size=10)\n    // note: negative value means unlimited\n    \"size\": number,    \n\n    // Total Pages\n    \"total_pages\": number,\n\n    // Max Page\n    // start from 0 until last index\n    // example: \n    //   if you have 3 pages (page0, page1, page2)\n    //   max_page is 2 not 3\n    \"max_page\": number,\n\n    // Last Page is true if the page \n    // has reached the end of the page\n    \"last\": bool,\n\n    // First Page is true if the page is 0\n    \"first\": bool,\n\n    // Visible\n    // total visible items\n    \"visible\": number,\n\n    // Error\n    // true if an error has occurred and\n    // paginate.Config.ErrorEnabled is true\n    \"error\": bool,\n\n    // Error Message\n    // current error if available and\n    // paginate.Config.ErrorEnabled is true\n    \"error_message\": string,\n}\n```\n## Paginate using http request\nexample paging, sorting and filtering:  \n1. `http://localhost:3000/?size=10\u0026page=0\u0026sort=-name`  \n    produces:\n    ```sql\n    SELECT * FROM user ORDER BY name DESC LIMIT 10 OFFSET 0\n    ```\n    `JSON` response:  \n    ```js\n    {\n        // result items\n        \"items\": [\n            {\n                \"id\": 1,\n                \"name\": \"john\",\n                \"age\": 20\n            }\n        ],\n        \"page\": 0, // current selected page\n        \"size\": 10, // current limit or size per page\n        \"max_page\": 0, // maximum page\n        \"total_pages\": 1, // total pages\n        \"total\": 1, // total matches including next page\n        \"visible\": 1, // total visible on current page\n        \"last\": true, // if response is first page\n        \"first\": true // if response is last page\n    }\n    ```\n2. `http://localhost:3000/?size=10\u0026page=1\u0026sort=-name,id`  \n    produces:\n    ```sql\n    SELECT * FROM user ORDER BY name DESC, id ASC LIMIT 10 OFFSET 10\n    ```\n3. `http://localhost:3000/?filters=[\"name\",\"john\"]`  \n    produces:\n    ```sql\n    SELECT * FROM user WHERE name = 'john' LIMIT 10 OFFSET 0\n    ```\n4. `http://localhost:3000/?filters=[\"name\",\"like\",\"john\"]`  \n    produces:\n    ```sql\n    SELECT * FROM user WHERE name LIKE '%john%' LIMIT 10 OFFSET 0\n    ```\n5. `http://localhost:3000/?filters=[\"age\",\"between\",[20, 25]]`  \n    produces:\n     ```sql\n    SELECT * FROM user WHERE ( age BETWEEN 20 AND 25 ) LIMIT 10 OFFSET 0\n    ```\n6. `http://localhost:3000/?filters=[[\"name\",\"like\",\"john%25\"],[\"OR\"],[\"age\",\"between\",[20, 25]]]`  \n    produces:\n     ```sql\n    SELECT * FROM user WHERE (\n        (name LIKE '%john\\%%' ESCAPE '\\') OR (age BETWEEN (20 AND 25))\n    ) LIMIT 10 OFFSET 0\n    ```\n7. `http://localhost:3000/?filters=[[[\"name\",\"like\",\"john\"],[\"AND\"],[\"name\",\"not like\",\"doe\"]],[\"OR\"],[\"age\",\"between\",[20, 25]]]`  \n    produces:\n     ```sql\n    SELECT * FROM user WHERE (\n        (\n            (name LIKE '%john%')\n                    AND\n            (name NOT LIKE '%doe%')\n        ) \n        OR \n        (age BETWEEN (20 AND 25))\n    ) LIMIT 10 OFFSET 0\n    ```\n8. `http://localhost:3000/?filters=[\"name\",\"IS NOT\",null]`  \n    produces:\n    ```sql\n    SELECT * FROM user WHERE name IS NOT NULL LIMIT 10 OFFSET 0\n    ```\n9. Using `POST` method:  \n   ```bash\n   curl -X POST \\\n   -H 'Content-type: application/json' \\\n   -d '{\"page\":1,\"size\":20,\"sort\":\"-name\",\"filters\":[\"name\",\"john\"]}' \\\n   http://localhost:3000/\n   ```  \n10. You can bypass HTTP Request with [Custom Request](#programmatically-pagination).\n\n## Example usage\n\n### NetHTTP Example\n\n```go\npackage main\n\nimport (\n    \"github.com/morkid/paginate\"\n    ...\n)\n\nfunc main() {\n    // var db *gorm.DB\n    pg := paginate.New()\n\n    http.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n        stmt := db.Joins(\"User\").Model(\u0026Article{})\n        page := pg.With(stmt).Request(r).Response(\u0026[]Article{})\n        j, _ := json.Marshal(page)\n        w.Header().Set(\"Content-type\", \"application/json\")\n        w.Write(j)\n    })\n\n    log.Fatal(http.ListenAndServe(\":3000\", nil))\n}\n```\n\n### Fasthttp Example\n\n```go\npackage main\n\nimport (\n    \"github.com/morkid/paginate\"\n    ...\n)\n\nfunc main() {\n    // var db *gorm.DB\n    pg := paginate.New()\n\n    fasthttp.ListenAndServe(\":3000\", func(ctx *fasthttp.RequestCtx) {\n        stmt := db.Joins(\"User\").Model(\u0026Article{})\n        page := pg.With(stmt).Request(\u0026ctx.Request).Response(\u0026[]Article{})\n        j, _ := json.Marshal(page)\n        ctx.SetContentType(\"application/json\")\n        ctx.SetBody(j)\n    })\n}\n```\n\n### Mux Router Example\n```go\npackage main\n\nimport (\n    \"github.com/morkid/paginate\"\n    ...\n)\n\nfunc main() {\n    // var db *gorm.DB\n    pg := paginate.New()\n    app := mux.NewRouter()\n    app.HandleFunc(\"/\", func(w http.ResponseWriter, req *http.Request) {\n        stmt := db.Joins(\"User\").Model(\u0026Article{})\n        page := pg.With(stmt).Request(req).Response(\u0026[]Article{})\n        j, _ := json.Marshal(page)\n        w.Header().Set(\"Content-type\", \"application/json\")\n        w.Write(j)\n    }).Methods(\"GET\")\n    http.Handle(\"/\", app)\n    http.ListenAndServe(\":3000\", nil)\n}\n```\n\n### Fiber example\n\n```go\npackage main\n\nimport (\n    \"github.com/morkid/paginate\"\n    ...\n)\n\nfunc main() {\n    // var db *gorm.DB\n    pg := paginate.New()\n    app := fiber.New()\n    app.Get(\"/\", func(c *fiber.Ctx) error {\n        stmt := db.Joins(\"User\").Model(\u0026Article{})\n        page := pg.With(stmt).Request(c.Request()).Response(\u0026[]Article{})\n        return c.JSON(page)\n    })\n\n    app.Listen(\":3000\")\n}\n```\n\n### Echo example\n\n```go\npackage main\n\nimport (\n    \"github.com/morkid/paginate\"\n    ...\n)\n\nfunc main() {\n    // var db *gorm.DB\n    pg := paginate.New()\n    app := echo.New()\n    app.GET(\"/\", func(c echo.Context) error {\n        stmt := db.Joins(\"User\").Model(\u0026Article{})\n        page := pg.With(stmt).Request(c.Request()).Response(\u0026[]Article{})\n        return c.JSON(200, page)\n    })\n\n    app.Logger.Fatal(app.Start(\":3000\"))\n}\n```\n\n### Gin Example\n\n```go\npackage main\n\nimport (\n    \"github.com/morkid/paginate\"\n    ...\n)\n\nfunc main() {\n    // var db *gorm.DB\n    pg := paginate.New()\n    app := gin.Default()\n    app.GET(\"/\", func(c *gin.Context) {\n        stmt := db.Joins(\"User\").Model(\u0026Article{})\n        page := pg.With(stmt).Request(c.Request).Response(\u0026[]Article{})\n        c.JSON(200, page)\n    })\n    app.Run(\":3000\")\n}\n\n```\n\n### Martini Example\n\n```go\npackage main\n\nimport (\n    \"github.com/morkid/paginate\"\n    ...\n)\n\nfunc main() {\n    // var db *gorm.DB\n    pg := paginate.New()\n    app := martini.Classic()\n    app.Use(render.Renderer())\n    app.Get(\"/\", func(req *http.Request, r render.Render) {\n        stmt := db.Joins(\"User\").Model(\u0026Article{})\n        page := pg.With(stmt).Request(req).Response(\u0026[]Article{})\n        r.JSON(200, page)\n    })\n    app.Run()\n}\n```\n### Beego Example\n\n```go\npackage main\n\nimport (\n    \"github.com/morkid/paginate\"\n    ...\n)\n\nfunc main() {\n    // var db *gorm.DB\n    pg := paginate.New()\n    web.Get(\"/\", func(c *context.Context) {\n        stmt := db.Joins(\"User\").Model(\u0026Article{})\n        page := pg.With(stmt).Request(c.Request).Response(\u0026[]Article{})\n        c.Output.JSON(page, false, false)\n    })\n    web.Run(\":3000\")\n}\n```\n\n### jQuery DataTable Integration\n\n```js\nvar logicalOperator = \"OR\"\n\n$('#myTable').DataTable({\n\n    columns: [\n        {\n            title: \"Author\",\n            data: \"user.name\"\n        }, {\n            title: \"Title\",\n            data: \"title\"\n        }\n    ],\n\n    processing: true,\n    \n    serverSide: true,\n\n    ajax: {\n        cache: true,\n        url: \"http://localhost:3000/articles\",\n        dataSrc: function(json) {\n            json.recordsTotal = json.visible\n            json.recordsFiltered = json.total\n            return json.items\n        },\n        data: function(params) {\n            var custom = {\n                page: !params.start ? 0 : Math.round(params.start / params.length),\n                size: params.length\n            }\n\n            if (params.order.length \u003e 0) {\n                var sorts = []\n                for (var o in params.order) {\n                    var order = params.order[o]\n                    if (params.columns[order.column].orderable != false) {\n                        var sort = order.dir != 'desc' ? '' : '-'\n                        sort += params.columns[order.column].data\n                        sorts.push(sort)\n                    }\n                }\n                custom.sort = sorts.join()\n            }\n\n            if (params.search.value) {\n                var columns = []\n                for (var c in params.columns) {\n                    var col = params.columns[c]\n                    if (col.searchable == false) {\n                        continue\n                    }\n                    columns.push(JSON.stringify([col.data, \"like\", encodeURIComponent(params.search.value.toLowerCase())]))\n                }\n                custom.filters = '[' + columns.join(',[\"' + logicalOperator + '\"],') + ']'\n            }\n\n            return custom\n        }\n    },\n})\n```\n\n### jQuery Select2 Integration\n\n```js\n$('#mySelect').select2({\n    ajax: {\n        url: \"http://localhost:3000/users\",\n        processResults: function(json) {\n            json.items.forEach(function(item) {\n                item.text = item.name\n            })\n            // optional\n            if (json.first) json.items.unshift({id: 0, text: 'All'})\n\n            return {\n                results: json.items,\n                pagination: {\n                    more: json.last == false\n                }\n            }\n        },\n        data: function(params) {\n            var filters = [\n                [\"name\", \"like\", params.term]\n            ]\n\n            return {\n                filters: params.term ? JSON.stringify(filters) : \"\",\n                sort: \"name\",\n                page: params.page \u0026\u0026 params.page - 1 ? params.page - 1 : 0\n            }\n        },\n    }\n})\n```\n\n### Programmatically Pagination\n\n```go\npackage main\n\nimport (\n    \"github.com/morkid/paginate\"\n    ...\n)\n\nfunc main() {\n    // var db *gorm.DB\n    pg := paginate.New()\n    req := \u0026paginate.Request{\n        Page: 2,\n        Size: 20,\n        Sort: \"-publish_date\",\n        filters: []interface{}{\n            []interface{}{\"user.name\", \"like\", \"john\"},\n            []interface{}{\"and\"},\n            []interface{}{\"publish_date\", \"\u003e=\", \"2025-12-31\"},\n            []interface{}{\"and\"},\n            []interface{}{\"user.active\", \"=\", true},\n            []interface{}{\"and\"},\n            []interface{}{\"user.last_login\", \"is not\", nil},\n        }\n    }\n\n    stmt := db.Joins(\"User\").Model(\u0026Article{})\n    page := pg.With(stmt).\n        Request(req).\n        Response(\u0026[]Article{})\n\n}\n```\n\n\n## Filter format\n\nPaginate Filters was inspired by [Frappe Framework API](https://docs.frappe.io/framework/user/en/api/rest#listing-documents). This feature is very powerful to support deep search and keep it safe. The format of filter param is a json encoded of multidimensional array.  \nMaximum array members is three, first index is `column_name`, second index is `operator` and third index is `values`, you can also pass array to values.  \n\n```js\n// Format:\n[\"column_name\", \"operator\", \"values\"]\n\n// Example:\n[\"age\", \"=\", 20]\n// Shortcut:\n[\"age\", 20]\n\n// Produces:\n// WHERE age = 20\n```\n\nSingle array member is known as **Logical Operator**.\n```js\n// Example\n[[\"age\", \"=\", 20],[\"or\"],[\"age\", \"=\", 25]]\n\n// Produces:\n// WHERE age = 20 OR age = 25\n```\n\n\nYou are allowed to send array inside a value.  \n```js\n[\"age\", \"between\", [20, 30] ]\n// Produces:\n// WHERE age BETWEEN 20 AND 30\n\n[\"age\", \"not in\", [20, 21, 22, 23, 24, 25, 26, 26] ]\n// Produces:\n// WHERE age NOT IN(20, 21, 22, 23, 24, 25, 26, 26)\n```\n\nDefine chain columns with same value separated by comma.\n```js\n// Example 1\n[\"price,discount\", \"\u003e\", 10]\n// Produces:\n// WHERE price \u003e 10 OR discount \u003e 10\n\n// Example 2\n[\"deleted_at,expiration_date\", null]\n// Produces:\n// WHERE deleted_at IS NULL OR expiration_date IS NULL\n```\n\nYou can filter nested condition with deep array.  \n```js\n[\n    [\n        [\"age\", \"\u003e\", 20],\n        [\"and\"]\n        [\"age\", \"\u003c\", 30]\n    ],\n    [\"and\"],\n    [\"name\", \"like\", \"john\"],\n    [\"and\"],\n    [\"name\", \"like\", \"doe\"]\n]\n// Produces:\n// WHERE ( (age \u003e 20 AND age \u003c 20) and name like '%john%' and name like '%doe%' )\n```\n\nFor `null` value, you can send string `\"null\"` or `null` value, *(lower)*\n```js\n// Wrong request\n[ \"age\", \"is\", NULL ]\n[ \"age\", \"is\", Null ]\n[ \"age\", \"is not\", NULL ]\n[ \"age\", \"is not\", Null ]\n\n// Right request\n[ \"age\", \"is\", \"NULL\" ]\n[ \"age\", \"is\", \"Null\" ]\n[ \"age\", \"is\", \"null\" ]\n[ \"age\", \"is\", null ]\n[ \"age\", null ]\n[ \"age\", \"is not\", \"NULL\" ]\n[ \"age\", \"is not\", \"Null\" ]\n[ \"age\", \"is not\", \"null\" ]\n[ \"age\", \"is not\", null ]\n```\n\n## Customize default configuration\n\nYou can customize the default configuration with `paginate.Config` struct. \n\n```go\npg := paginate.New(\u0026paginate.Config{\n    DefaultSize: 50,\n})\n```\n\nConfig             | Type       | Default               | Description\n------------------ | ---------- | --------------------- | -------------\nOperator           | `string`   | `OR`                  | Default conditional operator if no operator specified.\u003cbr\u003eFor example\u003cbr\u003e`GET /user?filters=[[\"name\",\"like\",\"jo\"],[\"age\",\"\u003e\",20]]`,\u003cbr\u003eproduces\u003cbr\u003e`SELECT * FROM user where name like '%jo' OR age \u003e 20`\nFieldWrapper       | `string`   | `LOWER(%s)`           | FieldWrapper for `LIKE` operator *(for postgres default is: `LOWER((%s)::text)`)*\nDefaultSize        | `int64`    | `10`                  | Default size or limit per page\nPageStart          | `int64`    | `0`                   | Set start page, default `0` if not set. `total_pages` , `max_page` and `page` variable will be affected if you set `PageStart` greater than `0` \nLikeAsIlikeDisabled | `bool`    | `false`               | By default, paginate using Case Insensitive on `LIKE` operator. Instead of using `ILIKE`, you can use `LIKE` operator to find what you want. You can set `LikeAsIlikeDisabled` to `true` if you need this feature to be disabled.\nSmartSearchEnabled | `bool`     | `false`               | Enable smart search *(Experimental feature)*\nCustomParamEnabled | `bool`     | `false`               | Enable custom request parameter\nFieldSelectorEnabled | `bool`   | `false`               | Enable partial response with specific fields. Comma separated per field. eg: `?fields=title,user.name`\nSortParams         | `[]string` | `[]string{\"sort\"}`    | if `CustomParamEnabled` is `true`,\u003cbr\u003eyou can set the `SortParams` with custom parameter names.\u003cbr\u003eFor example: `[]string{\"sorting\", \"ordering\", \"other_alternative_param\"}`.\u003cbr\u003eThe following requests will capture same result\u003cbr\u003e`?sorting=-name`\u003cbr\u003eor `?ordering=-name`\u003cbr\u003eor `?other_alternative_param=-name`\u003cbr\u003eor `?sort=-name`\nPageParams         | `[]string` | `[]string{\"page\"}`    | if `CustomParamEnabled` is `true`,\u003cbr\u003eyou can set the `PageParams` with custom parameter names.\u003cbr\u003eFor example:\u003cbr\u003e`[]string{\"number\", \"num\", \"other_alternative_param\"}`.\u003cbr\u003eThe following requests will capture same result `?number=0`\u003cbr\u003eor `?num=0`\u003cbr\u003eor `?other_alternative_param=0`\u003cbr\u003eor `?page=0`\nSizeParams         | `[]string` | `[]string{\"size\"}`    | if `CustomParamEnabled` is `true`,\u003cbr\u003eyou can set the `SizeParams` with custom parameter names.\u003cbr\u003eFor example:\u003cbr\u003e`[]string{\"limit\", \"max\", \"other_alternative_param\"}`.\u003cbr\u003eThe following requests will capture same result `?limit=50`\u003cbr\u003eor `?limit=50`\u003cbr\u003eor `?other_alternative_param=50`\u003cbr\u003eor `?max=50`\nOrderParams         | `[]string` | `[]string{\"order\"}`    | if `CustomParamEnabled` is `true`,\u003cbr\u003eyou can set the `OrderParams` with custom parameter names.\u003cbr\u003eFor example:\u003cbr\u003e`[]string{\"order\", \"direction\", \"other_alternative_param\"}`.\u003cbr\u003eThe following requests will capture same result `?order=desc`\u003cbr\u003eor `?direction=desc`\u003cbr\u003eor `?other_alternative_param=desc`\nFilterParams       | `[]string` | `[]string{\"filters\"}` | if `CustomParamEnabled` is `true`,\u003cbr\u003eyou can set the `FilterParams` with custom parameter names.\u003cbr\u003eFor example:\u003cbr\u003e`[]string{\"search\", \"find\", \"other_alternative_param\"}`.\u003cbr\u003eThe following requests will capture same result\u003cbr\u003e`?search=[\"name\",\"john\"]`\u003cbr\u003eor `?find=[\"name\",\"john\"]`\u003cbr\u003eor `?other_alternative_param=[\"name\",\"john\"]`\u003cbr\u003eor `?filters=[\"name\",\"john\"]`\nFieldsParams       | `[]string` | `[]string{\"fields\"}`  | if `FieldSelectorEnabled` and `CustomParamEnabled` is `true`,\u003cbr\u003eyou can set the `FieldsParams` with custom parameter names.\u003cbr\u003eFor example:\u003cbr\u003e`[]string{\"fields\", \"columns\", \"other_alternative_param\"}`.\u003cbr\u003eThe following requests will capture same result `?fields=title,user.name`\u003cbr\u003eor `?columns=title,user.name`\u003cbr\u003eor `?other_alternative_param=title,user.name`\nCacheAdapter       | `*gocache.AdapterInterface` | `nil` | the cache adapter, see more about [cache config](#speed-up-response-with-cache).\nErrorEnabled       | `bool` | `false` | Show error message in pagination result.\n\n## Override results\n\nYou can override result with custom function.  \n\n```go\n// var db = *gorm.DB\n// var httpRequest ... net/http or fasthttp instance\n// Example override function\noverride := func(article *Article) {\n    if article.UserID \u003e 0 {\n        article.Title = fmt.Sprintf(\n            \"%s written by %s\", article.Title, article.User.Name)\n    }\n}\n\nvar articles []Article\nstmt := db.Joins(\"User\").Model(\u0026Article{})\n\npg := paginate.New()\npage := pg.With(stmt).Request(httpRequest).Response(\u0026articles)\nfor index := range articles {\n    override(\u0026articles[index])\n}\n\nlog.Println(page.Items)\n\n```\n\n## Field selector\nTo implement a custom field selector, struct properties must have a json tag with omitempty.\n\n```go\n// real gorm model\ntype User {\n    gorm.Model\n    Name string `json:\"name\"`\n    Age  int64  `json:\"age\"`\n}\n\n// fake gorm model\ntype UserNullable {\n    ID        *string    `json:\"id,omitempty\"`\n    CreatedAt *time.Time `json:\"created_at,omitempty\"`\n    UpdatedAt *time.Time `json:\"updated_at,omitempty\"`\n    Name      *string    `json:\"name,omitempty\"`\n    Age       *int64     `json:\"age,omitempty\"`\n}\n```\n\n```go\n// usage\nnameAndIDOnly := []string{\"name\",\"id\"}\nstmt := db.Model(\u0026User{})\n\npage := pg.With(stmt).\n   Request(req).\n   Fields(nameAndIDOnly).\n   Response([]\u0026UserNullable{})\n```\n\n```javascript\n// response\n{\n    \"items\": [\n        {\n            \"id\": 1,\n            \"name\": \"John\"\n        }\n    ],\n    ...\n}\n```\n## Dynamic field selector\nIf the request contains query parameter `fields` (eg: `?fieilds=name,id`), then the response will show only `name` and `id`. To activate this feature, please set `FieldSelectorEnabled` to `true`.\n```go\nconfig := paginate.Config{\n    FieldSelectorEnabled: true,\n}\n\npg := paginate.New(config)\n```\n\n## Speed up response with cache\nYou can speed up results without looking database directly with cache adapter. See more about [cache adapter](https://github.com/morkid/gocache).\n\n### In memory cache\nin memory cache is not recommended for production environment:\n```go\nimport (\n    \"github.com/morkid/gocache\"\n    ...\n)\n\nfunc main() {\n    ...\n    adapterConfig := gocache.InMemoryCacheConfig{\n        ExpiresIn: 1 * time.Hour,\n    }\n    pg := paginate.New(\u0026paginate.Config{\n        CacheAdapter: gocache.NewInMemoryCache(adapterConfig),\n    })\n\n    page := pg.With(stmt).\n               Request(req).\n               Cache(\"article\"). // set cache name\n               Response(\u0026[]Article{})\n    ...\n}\n```\n\n### Disk cache\nDisk cache will create a file for every single request. You can use disk cache if you don't care about inode.\n```go\nimport (\n    \"github.com/morkid/gocache\"\n    ...\n)\n\nfunc main() {\n    adapterConfig := gocache.DiskCacheConfig{\n        Directory: \"/writable/path/to/my-cache-dir\",\n        ExpiresIn: 1 * time.Hour,\n    }\n    pg := paginate.New(\u0026paginate.Config{\n        CacheAdapter: gocache.NewDiskCache(adapterConfig),\n    })\n\n    page := pg.With(stmt).\n               Request(req).\n               Cache(\"article\"). // set cache name\n               Response(\u0026[]Article{})\n    ...\n}\n```\n\n### Redis cache\nRedis cache require [redis client](https://github.com/go-redis/redis) for golang.\n```go\nimport (\n    cache \"github.com/morkid/gocache-redis/v8\"\n    \"github.com/go-redis/redis/v8\"\n    ...\n)\n\nfunc main() {\n    client := redis.NewClient(\u0026redis.Options{\n        Addr:     \"localhost:6379\",\n        Password: \"\",\n        DB:       0,\n    })\n\n    adapterConfig := cache.RedisCacheConfig{\n        Client:    client,\n        ExpiresIn: 1 * time.Hour,\n    }\n    pg := paginate.New(\u0026paginate.Config{\n        CacheAdapter: cache.NewRedisCache(adapterConfig),\n    })\n\n    page := pg.With(stmt).\n               Request(req).\n               Cache(\"article\").\n               Response(\u0026[]Article{})\n    ...\n}\n```\n\u003e if your code already adopts another redis client, you can implement the [redis adapter](https://github.com/morkid/gocache-redis) according to its version. See more about [redis adapter](https://github.com/morkid/gocache-redis).\n\n### Elasticsearch cache\nElasticsearch cache require official [elasticsearch client](https://github.com/elastic/go-elasticsearch) for golang.\n```go\nimport (\n    cache \"github.com/morkid/gocache-elasticsearch/v7\"\n    \"github.com/elastic/go-elasticsearch/v7\"\n    ...\n)\n\nfunc main() {\n    config := elasticsearch.Config{\n        Addresses: []string{\n            \"http://localhost:9200\",\n        },\n    }\n    es, err := elasticsearch.NewClient(config)\n    if nil != err {\n        panic(err)\n    }\n\n    adapterConfig := cache.ElasticCacheConfig{\n        Client:    es,\n        Index:     \"exampleproject\",\n        ExpiresIn: 1 * time.Hour,\n    }\n    pg := paginate.New(\u0026paginate.Config{\n        CacheAdapter: cache.NewElasticCache(adapterConfig),\n    })\n\n    page := pg.With(stmt).\n               Request(req).\n               Cache(\"article\").\n               Response(\u0026[]Article{})\n    ...\n}\n```\n\u003e if your code already adopts another elasticsearch client, you can implement the [elasticsearch adapter](https://github.com/morkid/gocache-elasticsearch) according to its version. See more about [elasticsearch adapter](https://github.com/morkid/gocache-elasticsearch).\n\n### Custom cache\nCreate your own cache adapter by implementing [gocache AdapterInterface](https://github.com/morkid/gocache/blob/master/gocache.go). See more about [cache adapter](https://github.com/morkid/gocache).\n```go\n// AdapterInterface interface\ntype AdapterInterface interface {\n    // Set cache with key\n    Set(key string, value string) error\n    // Get cache by key\n    Get(key string) (string, error)\n    // IsValid check if cache is valid\n    IsValid(key string) bool\n    // Clear clear cache by key\n    Clear(key string) error\n    // ClearPrefix clear cache by key prefix\n    ClearPrefix(keyPrefix string) error\n    // Clear all cache\n    ClearAll() error\n}\n```\n\n### Clean up cache\nClear cache by cache name\n```go\npg.ClearCache(\"article\")\n```\nClear multiple cache\n```go\npg.ClearCache(\"cache1\", \"cache2\", \"cache3\")\n```\n\nClear all cache\n```go\npg.ClearAllCache()\n```\n\n\n## Limitations\n\nPaginate doesn't support has many relationship. You can make API with separated endpoints for parent and child:\n```javascript\nGET /users\n\n{\n    \"items\": [\n        {\n            \"id\": 1,\n            \"name\": \"john\",\n            \"age\": 20,\n            \"addresses\": [...] // doesn't support\n        }\n    ],\n    ...\n}\n```\n\nBest practice:\n\n```javascript\nGET /users\n{\n    \"items\": [\n        {\n            \"id\": 1,\n            \"name\": \"john\",\n            \"age\": 20\n        }\n    ],\n    ...\n}\n\nGET /users/1/addresses\n{\n    \"items\": [\n        {\n            \"id\": 1,\n            \"name\": \"home\",\n            \"street\": \"home street\"\n            \"user\": {\n                \"id\": 1,\n                \"name\": \"john\",\n                \"age\": 20\n            }\n        }\n    ],\n    ...\n}\n```\n\nPaginate doesn't support for customized json or table field name.  \nMake sure your struct properties have same name with gorm column and json property before you expose them.  \n\nExample bad configuration:  \n\n```go\n\ntype User struct {\n    gorm.Model\n    UserName       string `gorm:\"column:nickname\" json:\"name\"`\n    UserAddress    string `gorm:\"column:user_address\" json:\"address\"`\n}\n\n// request: GET /path/to/endpoint?sort=-name,address\n// response: \"items\": [] with sql error (column name not found)\n```\n\nBest practice:\n```go\ntype User struct {\n    gorm.Model\n    Name       string `gorm:\"column:name\" json:\"name\"`\n    Address    string `gorm:\"column:address\" json:\"address\"`\n}\n\n```\n\n## License\n\nPublished under the [MIT License](https://github.com/morkid/paginate/blob/master/LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorkid%2Fpaginate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmorkid%2Fpaginate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorkid%2Fpaginate/lists"}