Minimum Goyave version: v4.0.0-rc1
goyave.dev/filter allows powerful filtering using query parameters. Inspired by nestjsx/crud.
go get goyave.dev/filterFirst, 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
pageisn't given, the first page will be returned. - If
per_pageisn'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
omitemptyon 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.Timefrom thegopkg.in/guregu/null.v4library instead ofsql.NullTime. - Always specify
gorm:"foreignKey", otherwise falls back to "ID". - Don't use
gorm.Modeland 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)