# Eager Loading

# Introduction

The JSON:API include query parameter allows a client to customize which related resources should be returned in a response document. If the include parameter is provided, the response document will be a Compound Document (opens new window) containing the subject of the request in the data member, and the related resources in the top-level included member.

In Laravel terms, the include query parameter allows the client to control eager loading. (opens new window) Each JSON:API include path maps to an Eloquent eager load path.

Eager loading via the include parameter allows a client to reduce the number of HTTP requests. Ideally, a client should be able to retrieve the resources they need in a single request for a Compound Document.

# Eager Loading Depth

Our implementation allows you to specify the depth to which a client can request eager loading. This is defined on the resource type's schema and by default is set to a depth of 1.

To illustrate this, take the following PostSchema:

use LaravelJsonApi\Eloquent\Fields\ID;
use LaravelJsonApi\Eloquent\Fields\Relations\BelongsTo;
use LaravelJsonApi\Eloquent\Fields\Relations\HasMany;

/**
 * @inheritDoc
 */
public function fields(): array
{
    return [
        ID::make(),
        // ...attribute fields
        BelongsTo::make('user'),
        HasMany::make('tags'),
        HasMany::make('comments'),
    ];
}

With a default depth of 1, the client will be allowed to request the following include paths:

  • user
  • tags
  • comments

If we increase the max depth to 2, the client will be allowed to request paths through the relationships. For example:

  • user
  • user.profile
  • tags
  • comments
  • comments.user

# Setting the Depth

To set the depth of include paths on a resource type, use the $maxDepth property:

class PostSchema extends Schema
{

    /**
    * The maximum depth of include paths.
    *
    * @var int
    */
    protected int $maxDepth = 3;
}

WARNING

You probably do not want to use a large maximum depth - typically, a maximum value of 3 or 4 is ideal. For example, the Stripe API (opens new window) allows a maximum depth of 4 levels.

# Disabling On Specific Relations

Typically you should prevent eager loading on relationships that could (even if only in theory) return hundreds of resources. This is because it would be exceptionally inefficient for a client to get a compound document that contains hundreds or thousands of related resources.

Take for example a posts resource with a comments relationship. If our blog application is extremely popular, a post could have hundreds or thousands of comments. In such a scenario, we would want to prevent the comments relationship from being eager loaded, by using the cannotEagerLoad method:

HasMany::make('comments')->cannotEagerLoad()

TIP

In this scenario, our include path depth implementation will know not to allow any paths through the relation that is marked as not allowed for eager loading.

In this scenario, we would instead expect an API client to receive paginated comments for a posts resource, by making a request to the comments relationship endpoint:

GET /api/v1/posts/123/comments?page[size]=20&page[number]=1&include=user HTTP/1.1
Accept: application/vnd.api+json

In this request, the client asks for the first 20 comments for the posts resource with an id of 123. It also asks for the user of the comments to be included, so that it can display each comment with the user who created it.

TIP

As this relationship endpoint returns comments resources, the allowed include paths are determined by the CommentSchema class.

# Conditionally Disabling on Specific Relations

If you need to conditionally disable eager-loading on a relationship, use the canEagerLoad() method. You can provide a boolean, for example:

HasMany::make('comments')->canEagerLoad(
    Gate::allows('viewComments', Post::class),
)

Alternatively, you can provide a callback to determine if eager loading is allowed:

HasMany::make('comments')->canEagerLoad(
    fn() => Gate::allows('viewComments', Post::class),
)

# Disabling On A Resource Type

To disable eager loading for a resource type, set the $maxDepth property to zero:

class PostSchema extends Schema
{

    /**
    * The maximum depth of include paths.
    *
    * @var int
    */
    protected int $maxDepth = 0;
}

# Specifying Custom Paths

If you do not want to use our depth implementation, you can specify allowed include paths on a resource type by implementing the includePaths method on your schema. For example:

public function includePaths(): iterable
{
    return [
      'comments',
      'comments.user',
      'tags',
    ];
}

# Eager Loading Attribute Values

If any of your resource's attributes are derived from related models, you should use the attribute's on() method to set the relationship. This is described in the attributes chapter, but an example looks like this:

Str::make('description')->on('profile')

When you do this, we will automatically eager load the relationships from which the attributes are derived.

# Additional Eager Loading

If for any reason there are additional relationships that you always need to eager load, you can add the relationship to the $with property of your schema. This property instructs the schema to always eager load the listed model relationships when retrieving the resource.

For example:

/**
 * The relationships that should be eager loaded.
 *
 * @var array
 */
protected array $with = ['profile'];

TIP

Generally you will not need to add default eager load paths. If a model relationship is available on your resource as a JSON:API relationship, then eager loading is controlled via the include query parameter. If eager loading is required for an attribute, you should use the attribute's on() method to specify the relationship that needs to be eager loaded.

# Default Include Paths

If you want to set include paths that should be used when the client provides none, these must be set on the query parameters request class for the resource, e.g. PostQuery and PostCollectionQuery. Use the $defaultIncludePaths property, as described in the query parameters chapter.

Last Updated: 5/7/2024, 3:14:43 AM