Features

Including Relationships

Eager-load Lucid relationships, nested relations, and relationship counts from API requests.

The include query parameter allows clients to eager-load Lucid relationships (preload) and relationship counts (withCount) on demand.

All includes must be explicitly allowed via .allowedIncludes() on the query builder or via the includes property on a Filterable Class.

Basic Usage

Pass relationship names defined on your Lucid model to .allowedIncludes():

// GET /users?include=posts,profile
// Calls query.preload('posts') and query.preload('profile')
const users = await User.query()
  .withRequest(request)
  .allowedIncludes('posts', 'profile');
// Or pass an array:
// .allowedIncludes(['posts', 'profile'])

If a client requests an include that is not allowed, an InvalidIncludeQuery exception (400 Bad Request) is thrown unless disableInvalidIncludesQueryException is enabled in Configuration.

Nested Relationships

You can allow nested relationships using dot-notation (posts.comments). Allowing a nested relationship automatically allows all parent segments along the path (posts and posts.comments):

// Allows ?include=posts, ?include=posts.comments, and ?include=posts.comments.author
const users = await User.query()
  .withRequest(request)
  .allowedIncludes('posts.comments.author');

Including Relationship Counts

Whenever you allow a top-level relationship with AllowedInclude.relationship('posts') (or by passing 'posts' as a string), the package automatically registers a count include suffixed with Count (configurable via countSuffix in config/apiquery.ts).

// GET /users?include=postsCount
// Executes query.withCount('posts'), exposing $extras.posts_count on each user
const users = await User.query()
  .withRequest(request)
  .allowedIncludes('posts');

If you only want to allow counting a relationship without allowing the full relationship records to be preloaded, use AllowedInclude.count() or pass 'postsCount' directly:

import { AllowedInclude } from '@eienjs/adonisjs-api-query';

// Allows ?include=postsCount, but rejects ?include=posts
const users = await User.query()
  .withRequest(request)
  .allowedIncludes([
    AllowedInclude.count('postsCount'),
  ]);

Aliasing Includes

Use AllowedInclude.relationship(name, internalName) or AllowedInclude.count(name, internalName) to expose a public include name that maps to a different relationship name on your model:

import { AllowedInclude } from '@eienjs/adonisjs-api-query';

// GET /users?include=articles -> preloads the `posts` relationship
// GET /users?include=articlesCount -> calls withCount('posts')
const users = await User.query()
  .withRequest(request)
  .allowedIncludes([
    AllowedInclude.relationship('articles', 'posts'),
  ]);

Callback Includes

Use AllowedInclude.callback() when you want to customize the preload subquery (for example, selecting specific columns, ordering related records, or filtering the preloaded relation):

import { AllowedInclude } from '@eienjs/adonisjs-api-query';

// GET /users?include=latestPosts
const users = await User.query()
  .withRequest(request)
  .allowedIncludes([
    AllowedInclude.callback('latestPosts', (postsQuery) => {
      void postsQuery
        .where('isPublished', true)
        .orderBy('createdAt', 'desc')
        .limit(5);
    }, 'posts'),
  ]);
When using Filterable Classes, you can also define an include<Name>(query) method on your class and list 'name' in public includes = ['name']—it will automatically be bound as a callback include!

Custom Include Classes

For reusable include strategies across multiple models, implement the Include<ParentModel> interface and register it with AllowedInclude.custom(). See Custom Filters, Sorts & Includes for details.