Validation
@eienjs/adonisjs-api-query provides first-class, optional integration with @vinejs/vine to validate incoming query parameters (filter, sort, include, etc.) before executing database queries.
@vinejs/vine (>= 4.4.0) is an optional peer dependency. If your project does not use validation rules, you do not need @vinejs/vine installed.Defining Validation Rules
You can define validation rules in two places (or combine both):
- Per-filter rules using
AllowedFilter.rule(schema). - Request-level rules using the
rulesproperty on aBaseFilterableclass (or viaapiRequest.setRules()).
1. Per-Filter Rules with .rule()
Attach a VineJS schema directly to any AllowedFilter:
import { AllowedFilter } from '@eienjs/adonisjs-api-query';
import vine from '@vinejs/vine';
const users = await User.query()
.withRequest(request)
.allowedFilters([
AllowedFilter.partial('name').rule(vine.string().minLength(2)),
AllowedFilter.exact('age').rule(vine.number().min(18)),
AllowedFilter.exact('status').rule(vine.enum(['active', 'inactive', 'pending'])),
AllowedFilter.exact('createdAt').rule(vine.date()),
]);
.rule(...) is automatically made .optional() under filter.<name> when the validator is compiled, while preserving all type conversions and constraints whenever the client provides that filter.2. Rules on a Filterable Class
On a BaseFilterable class, you can define public rules as:
- A plain object of VineJS schemas (
Record<string, SchemaTypes>) - A
vine.object({...})schema - A pre-compiled
vine.create(...)validator - Or a method
public rules() { ... }returning any of the above
import { AllowedFilter, BaseFilterable } from '@eienjs/adonisjs-api-query';
import vine from '@vinejs/vine';
import User from '#models/user';
export default class UserFilterable extends BaseFilterable.for(() => User) {
public filters = [
'name',
'status',
AllowedFilter.exact('age').rule(vine.number().min(18)),
];
public sorts = ['name', 'createdAt'];
public includes = ['posts'];
public rules = vine.object({
filter: vine.object({
name: vine.string().trim().minLength(2).optional(),
status: vine.enum(['active', 'inactive']).optional(),
age: vine.number().min(18).optional(),
}).optional(),
sort: vine.string().optional(),
include: vine.string().optional(),
});
}
filter from this.rules (for example, only validating sort or page in rules), any per-filter .rule(...) definitions from this.filters are automatically merged into the filter key of the compiled validator.Automatic vs. Manual Validation
Automatic Validation (Default)
When autoValidate is true (the default), any query that has validation rules configured—either via BaseFilterable.rules or AllowedFilter.rule()—automatically validates the request right before the query is executed (execQuery).
import type { HttpContext } from '@adonisjs/core/http';
import User from '#models/user';
export default class UsersController {
public async index({ request }: HttpContext) {
// Automatically validates using VineJS before running the SQL query!
// Throws E_VALIDATION_ERROR (HTTP 422) if validation fails.
return await User.$filter(request).paginate(1, 15);
}
}
When request.validateUsing is available on the AdonisJS HttpRequest, the package uses it so standard AdonisJS error reporters and content negotiation work seamlessly. Furthermore, validated and transformed values from filter (such as trimmed strings or coerced numbers/booleans) are used when reading filter values.
Disabling Automatic Validation
You can disable automatic validation for a single filterable class or globally across your app:
import { BaseFilterable } from '@eienjs/adonisjs-api-query';
import User from '#models/user';
export default class UserFilterable extends BaseFilterable.for(() => User) {
public autoValidate = false;
}
import { defineConfig } from '@eienjs/adonisjs-api-query';
export default defineConfig({
autoValidate: false,
});
Explicit / Manual Validation
You can trigger validation explicitly at any time using:
import { ApiQueryBuilderRequest } from '@eienjs/adonisjs-api-query';
import vine from '@vinejs/vine';
// 1. Via the Filterable class static methods:
const validatedPayload = await UserFilterable.validate(request);
// Or validate and obtain the query builder in one step:
const query = await UserFilterable.$validateAndQuery(request);
const users = await query.paginate(1, 15);
// 2. Via the query builder `.validate()` macro:
const userQuery = User.$filter(request);
await userQuery.validate();
const results = await userQuery;
// 3. Directly via ApiQueryBuilderRequest in a controller:
const apiRequest = new ApiQueryBuilderRequest(request);
const data = await apiRequest.validate({
filter: vine.object({
name: vine.string().minLength(3).optional(),
}).optional(),
});
ApiQueryBuilderRequest instance, so calling .validate() manually before executing a query with autoValidate: true will not run validation twice.