Features

Validation

Validate API query parameters automatically or explicitly using VineJS.

@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):

  1. Per-filter rules using AllowedFilter.rule(schema).
  2. Request-level rules using the rules property on a BaseFilterable class (or via apiRequest.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()),
  ]);
Because filters in a URL are optional by nature, any schema passed to .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
app/filters/user_filterable.ts
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(),
  });
}
If you omit 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).

app/controllers/users_controller.ts
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;
}

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(),
});
Validation is memoized per ApiQueryBuilderRequest instance, so calling .validate() manually before executing a query with autoValidate: true will not run validation twice.