Minimum Goyave version: v4.0.0-rc1
goyave.dev/filter
allows powerful filtering using query parameters. Inspired by nestjsx/crud.
go get goyave.dev/filter
First, apply filters validation to the RuleSet
used on the routes you wish the filters on.
import "goyave.dev/filter"
//...
var (
IndexRequest = validation.RuleSet{}
)
func init() {
filter.ApplyValidation(IndexRequest)
}
router.Get("/users", user.Index).Validate(user.IndexRequest)
Then implement your controller handler:
import "goyave.dev/filter"
//...
func Index(response *goyave.Response, request *goyave.Request) {
var users []*model.User
paginator, tx := filter.Scope(database.GetConnection(), request, &users)
if response.HandleDatabaseError(tx) {
response.JSON(http.StatusOK, paginator)
}
}
And that's it! Now your front-end can add query parameters to filter as it wants.
You can disable certain features, or blacklist certain fields using filter.Settings
:
settings := &filter.Settings{
DisableFields: true, // Prevent usage of "fields"
DisableFilter: true, // Prevent usage of "filter"
DisableSort: true, // Prevent usage of "sort"
DisableJoin: true, // Prevent usage of "join"
Blacklist: filter.Blacklist{
// Prevent selecting, sorting and filtering on these fields
FieldsBlacklist: []string{"a", "b"},
// Prevent joining these relations
RelationsBlacklist: []string{"Relation"},
Relations: map[string]*filter.Blacklist{
// Blacklist settings to apply to this relation
"Relation": &filter.Blacklist{
FieldsBlacklist: []string{"c", "d"},
RelationsBlacklist: []string{"Parent"},
Relations: map[string]*filter.Blacklist{ /*...*/ },
IsFinal: true, // Prevent joining any child relation if true
},
},
},
}
paginator, tx := settings.Scope(database.GetConnection(), request, &results)
?filter=field||$operator||value
Examples:
?filter=name||$cont||Jack (
WHERE name LIKE %Jack%
)
You can add multiple filters. In that case, it is interpreted as an AND
condition.
You can use OR
conditions using ?or
instead, or in combination:
?filter=name||$cont||Jack&or=name||$cont||John (
WHERE name LIKE %Jack% OR name LIKE %John%
)
?filter=age||$eq||50&filter=name||$cont||Jack&or=name||$cont||John (WHERE age = 50 AND name LIKE %Jack% OR name LIKE %John%
)
You can filter using columns from one-to-one relations ("has one" or "belongs to"):
?filter=Relation.name||$cont||Jack
$eq |
= , equals |
$ne |
<> , not equals |
$gt |
> , greater than |
$lt |
< , lower than |
$gte |
>= , greater than or equals |
$lte |
<= , lower than or equals |
$starts |
LIKE val% , starts with |
$ends |
LIKE %val , ends with |
$cont |
LIKE %val% , contains |
$excl |
NOT LIKE %val% , not contains |
$in |
IN (val1, val2,...) , in (accepts multiple values) |
$notin |
NOT IN (val1, val2,...) , in (accepts multiple values) |
$isnull |
IS NULL , is NULL (doesn't accept value) |
$notnull |
IS NOT NULL , not NULL (doesn't accept value) |
$between |
BETWEEN val1 AND val2 , between (accepts two values) |
?fields=field1,field2
A comma-separated list of fields to select. If this field isn't provided, uses SELECT *
.
?sort=column,ASC|DESC
Examples:
?sort=name,ASC
?sort=age,DESC
You can also sort by multiple fields.
?sort=age,DESC&sort=name,ASC
?join=relation
Preload a relation. You can also only select the columns you need:
?join=relation||field1,field2
You can join multiple relations:
?join=profile||firstName,email&join=notifications||content&join=tasks
Internally, goyave.dev/filter
uses Goyave's Paginator
.
?page=1&per_page=10
- If
page
isn't given, the first page will be returned. - If
per_page
isn't given, the default page size will be used. This default value can be overridden by changingfilter.DefaultPageSize
. - Either way, the result is always paginated, even if those two parameters are missing.
- Inputs are escaped to prevent SQL injections.
- Fields are pre-processed and clients cannot request fields that don't exist. This prevents database errors. If a non-existing field is required, it is simply ignored. The same goes for sorts and joins. It is not possible to request a relation that doesn't exist.
- Foreign keys are always selected in joins to ensure associations can be assigned to parent model.
- Be careful with bidirectional relations (for example an article is written by a user, and a user can have many articles). If you enabled both your models to preload these relations, the client can request them with an infinite depth (
Articles.User.Articles.User...
). To prevent this, it is advised to use the relation blacklist or IsFinal on the deepest requestable models. See the settings section for more details.
- Use
json:",omitempty"
on all model fields.- Note: using
omitempty
on slices will remove them from the json result if they are not nil and empty. There is currently no solution to this problem using the standard json package.
- Note: using
- Use
json:"-"
on foreign keys. - Use
*null.Time
from thegopkg.in/guregu/null.v4
library instead ofsql.NullTime
. - Always specify
gorm:"foreignKey"
, otherwise falls back to "ID". - Don't use
gorm.Model
and add the necessary fields manually. You get better control over json struct tags this way. - Use pointers for nullable relations and nullable fields that implement
sql.Scanner
(such asnull.Time
).
goyave.dev/filter
is MIT Licensed. Copyright (c) 2021 Jérémy LAMBERT (SystemGlitch)