{"id":38096719,"url":"https://github.com/roneli/goqux","last_synced_at":"2026-01-16T21:03:48.077Z","repository":{"id":172860234,"uuid":"649868230","full_name":"roneli/goqux","owner":"roneli","description":"Library for simple CRUD, scanning \u0026 pagination using query builder in golang","archived":false,"fork":false,"pushed_at":"2025-04-20T07:29:33.000Z","size":69,"stargazers_count":22,"open_issues_count":4,"forks_count":8,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-20T08:31:44.108Z","etag":null,"topics":["go","golang","goqu","gorm","pgx","postgres","query-builder","sql","sql-builder"],"latest_commit_sha":null,"homepage":"","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/roneli.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,"zenodo":null}},"created_at":"2023-06-05T20:19:05.000Z","updated_at":"2025-04-20T07:27:12.000Z","dependencies_parsed_at":"2023-11-12T15:52:31.828Z","dependency_job_id":"5608c32f-c4eb-4696-90bc-db2efbc0b428","html_url":"https://github.com/roneli/goqux","commit_stats":null,"previous_names":["roneli/goqux"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/roneli/goqux","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roneli%2Fgoqux","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roneli%2Fgoqux/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roneli%2Fgoqux/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roneli%2Fgoqux/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/roneli","download_url":"https://codeload.github.com/roneli/goqux/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roneli%2Fgoqux/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28482495,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T11:59:17.896Z","status":"ssl_error","status_checked_at":"2026-01-16T11:55:55.838Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["go","golang","goqu","gorm","pgx","postgres","query-builder","sql","sql-builder"],"created_at":"2026-01-16T21:03:47.961Z","updated_at":"2026-01-16T21:03:48.057Z","avatar_url":"https://github.com/roneli.png","language":"Go","readme":"```\n                           ____  ___\n   ____   ____   ________ _\\   \\/  /\n  / ___\\ /  _ \\ / ____/  |  \\     / \n / /_/  \u003e  \u003c_\u003e \u003c \u003c_|  |  |  /     \\ \n \\___  / \\____/ \\__   |____/___/\\  \\\n/_____/            |__|          \\_/\n```\n\nGoquX is a lightweight wrapper library for [goqu](https://github.com/doug-martin/goqu), \ndesigned to simplify the process of building CRUD queries, implementing pagination, and struct scanning [scany](https://github.com/georgysavva/scany).\n\n## Features\n\n- [Builder helpers](#query-building-helpers) for **select**/**insert**/**update**/**delete** queries, auto adding columns and serialization of rows, tags for skipping columns/setting default values.\n- Query Execution support, with [Pagination](#pagination) for offset/limit and keyset pagination.\n- **Automatic** scanning into structs using [scany](https://github.com/georgysavva/scany). \n- **Customizable** [builder options](#easily-extend-with-builder-options), allowing you to easily extend the builder options.\n\n## Why?\n\nThere is much debate in Golang about the best way to handle database queries. Some prefer ORM libraries like GORM,\nwhile others prefer to use query builders like [goqu](https://github.com/doug-martin/goqu), and of course, \nthere are those who prefer to write raw SQL queries.\n\nPersonally, I usually like to use query builders as they offer a good balance, although when it's a very complex query use raw SQL instead.\n\nI wrote GoquX because I found myself writing the same code over and over again for simple queries, and I wanted to simplify \nthe process of building [CRUD](#query-building-helpers) queries, implementing [pagination](#pagination), and [struct scanning](#select).\n\nGoquX is not a replacement for [goqu](https://github.com/doug-martin/goqu), but rather a lightweight wrapper that simplifies the process of using it.\n\n\n## Installation\n\nTo use GoquX in your Go project, you need to have Go installed and set up on your machine. Then, run the following command to add GoquX as a dependency:\n\n```bash\ngo get github.com/roneli/goqux\n```\n\n## Pagination\n\n`goqux` adds a convenient pagination function allowing us to scan the results into a slice of structs, add filters, ordering, \nand extend the query with any other goqu function.\n\nPagination currently supports offset/limit or keyset pagination.\n\n#### Offset/Limit pagination\n\n```go \nconn, err := pgx.Connect(ctx, \"postgres://postgres:postgres@localhost:5432/postgres\")\nif err != nil {\n    log.Fatal(err)\n}\npaginator, err := goqux.SelectPagination[User](ctx, conn, \"users\", \u0026goqux.PaginationOptions{ PageSize: 100}, goqux.WithSelectFilters(goqux.Column(\"users\", \"id\").Eq(2)))\nfor paginator.HasMorePages() {\n    users, err := paginator.NextPage()\n    ...\n}\n```\n#### KeySet pagination\n\n[Keyset](https://www.cockroachlabs.com/docs/stable/pagination.html#:~:text=Keyset%20pagination%20(also%20known%20as,of%20WHERE%20and%20LIMIT%20clauses.)) pagination, using ordering and where filter keeping that last returned row as the key for the next page.\n\n```go\nconn, err := pgx.Connect(ctx, \"postgres://postgres:postgres@localhost:5432/postgres\")\nif err != nil {\n    log.Fatal(err)\n}\npaginator, err := goqux.SelectPagination[User](ctx, conn, \"users\", \u0026goqux.PaginationOptions{ PageSize: 100, Keyset:[\"id\"]}, goqux.WithSelectFilters(goqux.Column(\"users\", \"id\").Eq(2)))\nfor paginator.HasMorePages() {\n    users, err := paginator.NextPage()\n    ...\n}\n```\n\nfor querying on any query with keyset pagination, use the `goqux.PaginateQueryByKeySet` function.\n\n```go\npaginator, err := goqux.QueryKeySetPagination[User](ctx, conn, selectDataset, 100, []string{\"id\"})\nfor paginator.HasMorePages() {\n    users, err := paginator.NextPage()\n    ...\n}\n```\n\n### Test Pagination Queries\n\n```go\npaginatorMock := goqux.NewPaginator(func(p *goqux.Paginator[T]) ([]T, bool, error) {\n\tstopClause := true\n\tvar items []T{}\n\treturn items, stopClause, nil\n})\nEXPECT().ListItems().Return(paginatorMock, ...)\n```\n\n## Query building Helpers\n\n`goqux` adds select/insert/update/delete simple utilities to build queries.\n\n### Select Builder\n\n```go\ntype User struct {\n    ID        int64     `db:\"id\"`\n    Name      string    `db:\"name\"`\n    Email     string    `db:\"email\"`\n    CreatedAt time.Time `db:\"created_at\"`\n    UpdatedAt time.Time `db:\"updated_at\"`\n    FieldToSkip string  `goqux:\"skip_select\"`\n}\n// Easily extend the query with any other goqux function optional functions that get access to the query builder.\n// use goqux:\"skip_select\" to skip a field in the select query.\nsql, args, err := goqux.BuildSelect(\"table_to_select\", User{},\n    goqux.WithSelectFilters(goqux.Column(\"table_to_select\", \"id\").Gt(2)),\n    goqux.WithSelectOrder(goqux.Column(\"table_to_select\", \"id\").Desc()),\n    goqux.WithSelectLimit(10),\n)\n```\n\n### Insert Builder\n\n```go\ntype User struct {\n    ID        int64     `db:\"id\"`\n    Name      string    `db:\"name\"`\n    Email     string    `db:\"email\"`\n    CreatedAt time.Time `goqux:\"now,skip_update\"`\n    UpdatedAt time.Time `goqux:\"now_utc\"`\n    FieldToSkip string  `goqux:\"skip_insert\"`\n}\n// use goqux:\"now\" to set the current time in the insert query for CreatedAt, and goqux:\"now_utc\" to set the current time in UTC for UpdatedAt. \nsql, args, err := goqux.BuildInsert(\"table_to_insert\", User{ID: 5, Name: \"test\", Email: \"test@test.com\"}, goqu.WithReturningAll()),\n)\n```\n\n### Delete Builder\n\n```go\ntype User struct {\n    ID        int64     `db:\"id\"`\n    Name      string    `db:\"name\"`\n    Email     string    `db:\"email\"`\n    CreatedAt time.Time `goqux:\"now,skip_update\"`\n    UpdatedAt time.Time `goqux:\"now_utc\"`\n    FieldToSkip string  `goqux:\"skip_insert\"`\n}\nsql, args, err := goqux.BuildDelete(\"table_to_delete\", goqux.WithDeleteFilters(goqux.Column(\"delete_models\", \"id\").Eq(1), goqu.WithReturningAll()))\n```\n\n### Update Builder\n\n```go\ntype User struct {\n    ID        int64     `db:\"id\"`\n    Name      string    `db:\"name\"`\n    Email     string    `db:\"email\"`\n    CreatedAt time.Time `goqux:\"now,skip_update\"`\n    UpdatedAt time.Time `goqux:\"now_utc\"`\n}\n// will update only the name field for the user with id 1\nsql, args, err := goqux.BuildUpdate(\"table_to_update\", \u0026User{Name: \"goqux\"}, goqux.WithUpdateFilters(goqux.Column(\"table_to_update\", \"id\").Eq(1), goqu.WithReturningAll()))\n```\n\n## Select/Insert/Update/Delete Executions\n\n`goqux` adds select/insert/update/delete functions to execute simple queries.\n\n### SelectOne\n```go\nuser, err := goqux.SelectOne[User](ctx, conn, \"users\", goqux.WithSelectFilters(goqux.Column(\"users\", \"id\").Eq(2)))\n```\n\n### Select\n```go\nuser, err := goqux.Select[User](ctx, conn, \"users\",  goqux.WithSelectOrder(goqu.C(\"id\").Asc()))\n```\n\n### Insert\n\nWe can ignore the first returning value if we don't want to return the inserted row.\n```go\n_, err := goqux.Insert[User](ctx, conn, \"users\", tt.value)\n```\n\nIf we want to return the inserted row we can use the `goqux.WithInsertReturning` option.\n```go\nmodel, err := goqux.Insert[User](ctx, conn, \"users\", value, goqux.WithInsertDialect(\"postgres\"), goqux.WithInsertReturning(\"username\", \"password\", \"email\"))\n```\n\n### Update\n```go\n_, err := goqux.Update[User](ctx, conn, \"users\", value, goqux.WithUpdateFilters(goqux.Column(\"users\", \"id\").Eq(1)))\n```\n\n\n## Easily extend with builder options\nYou can define any custom option you want to extend the builder options, for example, if you want to add a group by option you can do the following:\n```go\nfunc WithSelectGroupBy(columns ...any) SelectOption {\n\treturn func(_ exp.IdentifierExpression, s *goqu.SelectDataset) *goqu.SelectDataset {\n\t\treturn s.GroupBy(columns...)\n\t}\n}\n```\n\nYou can add these options to any of the insert/update/delete/select functions.\n\n\n##### For more examples check the tests.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froneli%2Fgoqux","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Froneli%2Fgoqux","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froneli%2Fgoqux/lists"}