Introduction
@eienjs/adonisjs-api-query allows you to easily build Lucid ORM queries from incoming API requests in your AdonisJS v7 application. Inspired by Spatie's Laravel Query Builder and follows the JSON:API specification conventions for filtering, sorting, and including relationships.
Instead of manually inspecting request query parameters and conditionally chaining .where(), .orderBy(), and .preload() calls inside your controllers, you declare which filters, sorts, and includes are allowed—either inline on the query builder or inside dedicated Filterable Classes.
Key Features
- Filtering: Partial, exact, prefix/suffix, comparison operators (
>,<,>=,<=,<>), Lucid query scopes, soft-deletes (trashed), callbacks, custom filter classes, nested relationship constraints, ignored values, and default values. - Sorting: Ascending and descending multi-column sorting, default sorts, aliased fields, callbacks, and custom sort classes.
- Including Relationships: Preload top-level and nested relationships (
posts.comments), relationship counts (postsCount), custom callbacks, and custom include classes. - Filterable Classes: Encapsulate your query rules, default sorts, setup hooks, and custom
filter<Name>/sort<Name>/include<Name>methods in dedicated classes attached to your Lucid models. - Optional VineJS Validation: Automatically or explicitly validate incoming query parameters (
filter,sort,include) using@vinejs/vinebefore hitting the database. - Type-Safe: Built from the ground up with TypeScript, providing autocomplete for model attributes, scopes, and relationships.
Quick Example
Inline Query Builder Usage
Chain .allowedFilters(), .allowedSorts(), and .allowedIncludes() directly on any Lucid ModelQueryBuilder:
import type { HttpContext } from '@adonisjs/core/http';
import { AllowedFilter } from '@eienjs/adonisjs-api-query';
import User from '#models/user';
export default class UsersController {
public async index({ request }: HttpContext) {
// GET /users?filter[name]=john&filter[status]=active&include=posts&sort=-createdAt
return await User.query()
.withRequest(request)
.allowedFilters(['name', AllowedFilter.exact('status')])
.allowedIncludes(['posts'])
.allowedSorts(['name', 'createdAt'])
.defaultSort('-createdAt')
.paginate(1, 15);
}
}
Filterable Class Usage
For cleaner controllers and reusable query logic, encapsulate your configuration in a BaseFilterable class:
import { AllowedFilter, BaseFilterable } from '@eienjs/adonisjs-api-query';
import User from '#models/user';
export default class UserFilterable extends BaseFilterable.for(() => User) {
public filters = [
'name',
AllowedFilter.exact('email'),
'status',
];
public sorts = ['name', 'createdAt'];
public defaultSorts = '-createdAt';
public includes = ['posts', 'profile'];
public filterStatus(query, value: unknown) {
void query.where('status', String(value));
}
}
import { BaseModel, column } from '@adonisjs/lucid/orm';
import UserFilterable from '#filters/user_filterable';
export default class User extends BaseModel {
public static $filterable = () => UserFilterable;
@column({ isPrimary: true })
declare public id: number;
@column()
declare public name: string;
@column()
declare public email: string;
@column()
declare public status: string;
}
import type { HttpContext } from '@adonisjs/core/http';
import User from '#models/user';
export default class UsersController {
public async index({ request }: HttpContext) {
return await User.$filter(request).paginate(1, 15);
}
}
Next Steps
- Learn how to install and configure the package in Installation.
- Customize query parameter names and behavior in Configuration.
- Explore Filtering, Sorting, and Including Relationships.
