@eienjs/adonisjs-api-query
v3.0.1
Published
Easily build Lucid queries from API requests
Maintainers
Readme
@eienjs/adonisjs-api-query
Easily build Lucid queries from API requests for your AdonisJS v7 application.
Introduction
This package provides a set of macros on ModelQueryBuilder to build Lucid queries from API requests. Inspired by Spatie's Laravel Query Builder.
Quick Start
Install the package using the following command:
node ace add @eienjs/adonisjs-api-queryAutomatically the command will register the provider. Now you can use with your controller response:
import type { HttpContext } from '@adonisjs/core/http';
import User from '#models/user';
// inside a controller
export default class MyController {
public async handle({ request }: HttpContext) {
const users = await User.query()
.withRequest(request) // Pass the request to the query builder if async local storage is not enabled
.allowedFilters('name')
.allowedIncludes('posts')
.allowedSorts('id');
}
}Using Filterable Classes
Instead of configuring allowed filters, sorts, and includes directly in your controller, you can encapsulate them in a dedicated filterable class extending BaseFilterable:
// app/filters/user_filterable.ts
import { AllowedFilter, BaseFilterable } from '@eienjs/adonisjs-api-query';
import User from '#models/user';
export default class UserFilterable extends BaseFilterable.for(() => User) {
// Allowed filters: columns, AllowedFilter instances, or custom methods
public filters = [
'name',
'status',
AllowedFilter.exact('email'),
];
// Allowed sorts and default sorts
public sorts = ['name', 'createdAt'];
public defaultSorts = '-createdAt';
// Allowed relationship includes
public includes = ['posts', 'profile'];
// Optional setup hook to customize/initialize the query builder
public setup(query) {
query.where('isActive', true);
}
// Custom filter method (auto-bound to 'status' filter)
public filterStatus(query, value: unknown) {
void query.where('status', value as string);
}
}Attach the filterable class to your Lucid Model using static $filterable, @filterable decorator, or Filterable mixin:
// app/models/user.ts
import { BaseModel } from '@adonisjs/lucid/orm';
import UserFilterable from '#filters/user_filterable';
export default class User extends BaseModel {
// Using a lazy thunk prevents circular dependency issues in ESM
public static $filterable = () => UserFilterable;
}Now you can run the filtered query in controllers using User.$filter(request):
// inside a controller
export default class UsersController {
public async index({ request }: HttpContext) {
// Fluent query builder with filters, sorts, includes, and setup automatically applied
return await User.$filter(request).paginate(1, 15);
}
}You can also use it directly on existing queries via query.filter(request), or query directly from the filterable class:
// On an existing query builder instance
const admins = await User.query().where('role', 'admin').filter(request);
// With an explicit filterable class
const special = await User.$filter(SpecialUserFilterable, request);
// Or directly from the filterable class
const users = await UserFilterable.$query(request);Request Validation with VineJS (Optional)
You can validate incoming API query parameters (filter, sort, include, etc.) using @vinejs/vine. Validation is 100% optional: @vinejs/vine is declared as an optional peer dependency (>=4.4.0).
1. Defining Rules on a Filterable Class
Declare explicit validation rules using VineJS on your filterable class. You can pass a literal plain object, a vine.object(...), or a compiled vine.create(...) directly to rules:
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('createdAt').rule(vine.date()),
AllowedFilter.exact('age').rule(vine.number().min(18)),
];
public sorts = ['name', 'createdAt'];
// You can define rules as a literal object, vine.object(), or vine.create():
public rules = vine.object({
filter: vine.object({
name: vine.string().minLength(3).optional(),
status: vine.enum(['active', 'inactive']).optional(),
}).optional(),
sort: vine.string().optional(),
include: vine.string().optional(),
});
}2. Automatic & Explicit Validation
Automatic validation during query execution: When
rulesorvalidatorare defined on a filterable class (or onAllowedFilter.rule(...)), the query builder automatically executes validation with VineJS before querying the database. If validation fails, it throws a nativeValidationError(errors.E_VALIDATION_ERROR), which AdonisJS automatically transforms into HTTP 422 Unprocessable Entity:// Automatically validates before hitting the database: const users = await User.$filter(request).paginate(1, 15);Explicit validation & Controller usage: Validation resides directly inside
ApiQueryBuilderRequestas the central core of the package:// Validate request explicitly via filterable: const payload = await UserFilterable.validate(request); // Validate and get the query builder: const users = await UserFilterable.$validateAndQuery(request); // Or validate directly via ApiQueryBuilderRequest in controllers: const apiRequest = new ApiQueryBuilderRequest(request); const validated = await apiRequest.validate({ filter: vine.object({ name: vine.string() }).optional(), sort: vine.string().optional(), });Disabling automatic validation: You can disable automatic validation per-filterable with
public autoValidate = false;or globally inconfig/apiquery.tswithautoValidate: false.
Documentation
The documentation is available on EienJS.
Contributing
Contributions are welcome. Please read CONTRIBUTING for more details and remember to check the CHANGELOG file.
Copyright and License
The @eienjs/adonisjs-api-query library is licensed for use under the MIT License (MIT). Please see LICENSE for more information.
