Getting started
Install
go get github.com/dmedovich/gotq
gotq requires Go 1.23 or newer.
Define endpoint policy
Build an engine once during application startup:
type User struct {
ID uint `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
CreatedAt time.Time `json:"createdAt"`
}
policy := query.Schema[User]().
Expose("id", query.Sortable()).
Expose("name", query.Filterable(), query.Sortable()).
Expose("age", query.Filterable()).
Expose("createdAt", query.Filterable(), query.Sortable())
users, err := query.New(db, query.Config[User]{
Policy: policy,
DefaultLimit: 25,
MaxLimit: 100,
MaxOffset: 100_000,
AllowCount: true,
})
if err != nil {
panic(err)
}
Expose is the client whitelist. Go types, columns, and primary keys are
inferred through the actual GORM configuration; undisclosed fields stay private.
Execute a list
func listUsers(users *query.Engine[User], db *gorm.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
base := db.Where("tenant_id = ?", tenantID(r))
page, err := users.From(base).List(r.Context(), r.URL.Query())
if err != nil {
queryhttp.WriteError(w, err)
return
}
_ = json.NewEncoder(w).Encode(page)
}
}
The base scope is preserved for data and count. An omitted limit uses 25;
sorting is made deterministic with the model primary key.
If another page exists, return page.nextCursor to the client and accept it
unchanged as the next request's cursor while omitting offset.
Try a request
GET /users?filter=age gt 18 and name contains 'ann'&sort=-createdAt&limit=20&count=true
Advanced callers can use Engine.Parse and Engine.Apply, or the package-level
ParseHTTP and Apply, without executing a database statement.
For explicit to-one paths and to-many any/all, continue with
Relationships.
For deep forward traversal, continue with
Cursor pagination.