Getting Started

Introduction

Easily build Lucid queries from API requests in AdonisJS v7.

@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/vine before 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:

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

Next Steps