Sorting
Introduction
The JSON:API specification reserves the sort
query parameter for sorting resources. Sorting allows clients to specify the order in which resources are to be returning in JSON:API responses.
In Laravel JSON:API, you can mark attributes that represent database columns as sortable. You can also attach additional sort fields, that give you complete control over how models should be sorted. This chapter explains these capabilities.
Example Requests
Sorting is applied when:
- Fetching resources, e.g.
GET /api/posts
- Fetching related resources in a to-many relationship, e.g.
GET /api/v1/users/1/posts
. - Fetching relationship identifiers in a to-many relationship, e.g
GET /api/v1/users/1/relationships/posts
.
As an example, imagine our posts
resource has title
and createdAt
sort parameters.
This request would return posts
resources with the most recently created first (descending order):
GET /api/v1/posts?sort=-createdAt HTTP/1.1
Accept: application/vnd.api+json
This request would return posts
resources that were authored by user 1
, sorted by title
ascending, then createdAt
ascending:
GET /api/v1/users/1/posts?sort=title,createdAt HTTP/1.1
Accept: application/vnd.api+json
This request would return the resource identifiers of any post that was authored by user 1
, sorted by the post's createdAt
attribute in ascending order:
GET /api/v1/users/1/relationships/posts?sort=createdAt HTTP/1.1
Accept: application/vnd.api+json
Defining Sort Fields
ID Field
When attaching the ID
field to a schema, we assume that you want to allow an API client to sort your resources by the id
. Therefore resources that represent Eloquent models are always sortable by using the id
sort field, for example:
GET /api/v1/posts?sort=id HTTP/1.1
Accept: application/vnd.api+json
If you do not want your resource to be sortable by id
, use the notSortable()
method to remove sorting:
ID::make()->notSortable()
Attributes
When attaching an attribute to a schema, you may use the sortable
method to indicate that the resource can be sorted by the given field. For example:
DateTime::make('createdAt')->sortable(),
Str::make('title')->sortable(),
This works for attributes that relate to a database column that can be sorted.
Sortable Fields
Our sort field classes can be used to describe additional sort fields that a client is allowed to send for a particular resource type. To add a sort field to a schema, we can simply add it to the shcema's sortables()
method.
To create a sort field, we use the static make
method. For example, if we wanted to add a sort field to our posts
resource:
namespace App\JsonApi\V1\Posts;
use LaravelJsonApi\Eloquent\Schema;
use LaravelJsonApi\Eloquent\Sorting\SortCountable;
class PostSchema extends Schema
{
// ...
public function sortables(): iterable
{
return [
SortCountable::make($this, 'comments'),
];
}
}
The available sort field classes are described below, along with how to write your own sort fields as needed.
Default Sort Order
If desired, you can set a default sort order for a specific resource via the $defaultSort
property on the schema. This sort order will be used as the sort order when none is specified by the client.
For example, if we wanted our posts
to be returned in descending created date by default:
namespace App\JsonApi\V1\Posts;
use LaravelJsonApi\Eloquent\Schema;
class PostSchema extends Schema
{
protected $defaultSort = '-createdAt';
// ...
}
TIP
Note that the sort order is the JSON:API sort field, not the Eloquent column name. We also use a minus prefix (-
) to denote descending order, as per the JSON:API specification.
You can also set multiple default sort fields by using an array:
protected $defaultSort = ['-createdAt', 'title'];
Available Sort Fields
Laravel JSON:API ships with the following sort field classes:
TIP
If you want to add additional sort field classes, submit a Pull Request to the laravel-json-api/eloquent
repository.
SortColumn
The SortColumn
class adds a sort field that relates to a database column. Typically database columns will be exposed as JSON:API resource attributes, in which case you should use the sortable()
method on the attribute field class. The SortColumn
class is therefore intended for use when you need to allow a client to sort by a database column that is not exposed as a resource attribute.
Use the static make()
method to add the field to your sortables()
method. For example:
use LaravelJsonApi\Eloquent\Sorting\SortColumn;
SortColumn::make('publishedAt')
The first argument provided to the make()
method is the JSON:API sort field name. The database column is expected to be the underscored version of the field name - so published_at
in our above example. If the column name does not follow this convention, provide it as the second argument to the make()
method:
SortColumn::make('publishedAt', 'published')
SortCountable
The SortCountable
class allows a client to sort resources by a countable to-many relation that exists on your schema. Provide the schema and the relationship name to the static make()
method. For example:
use LaravelJsonApi\Eloquent\Sorting\SortCountable;
SortCountable::make($this, 'comments')
The client can then sort posts
using the comments
sort field:
GET /api/v1/posts?sort=-comments HTTP/1.1
Accept: application/vnd.api+json
In this example, the SortCountable
class expects the comments
relationship to be defined on the schema, and for the JSON:API sort field to also be called comments
. If you want the JSON:API field name to be something different from the relationship name, use the third argument to specify the JSON:API sort field name:
SortCountable::make($this, 'comments', 'totalComments')
In this example, the schema expects the relationship to be called comments
, while the client must use the totalComments
JSON:API sort field.
SortWithCount
The SortWithCount
class allows a client to sort resources by a relationship on the Eloquent model the resource represents. As the SortCountable
class should be used for a relationship that exists on your schema, the SortWithCount
class is used for relationships that exist on the model but not on the resource schema.
For example, let's imaging our Post
model has a likes
relationship. We can allow a client to sort posts
by the number of likes by adding the SortWithCount
class:
use LaravelJsonApi\Eloquent\Sorting\SortWithCount;
SortWithCount::make('likes')
In this example, the relationship name of the model is expected to be likes
, and the JSON:API sort field is also likes
. The client can then sort posts
using the likes
sort field:
GET /api/v1/posts?sort=-likes HTTP/1.1
Accept: application/vnd.api+json
If you want the JSON:API sort field name to be different from the relationship name, provide it as the second function argument:
SortWithCount::make('likes', 'totalLikes')
Laravel allows you to alias the relationship count, which is typically used when there might be a collision with a column name or you are using multiple counts for the same relationship. Use the countAs()
method if you need to alias the count for the sort:
SortWithCount::make('likes')->countAs('total_likes')
Finally, you can also constrain the count by providing a closure to the using()
method. This receives the query builder instance that is used for the count:
SortWithCount::make('likes')
->countAs('approved_likes_count')
->using(function ($query) {
$query->where('approved', true);
});
Writing Sort Fields
As your implementation grows, you will probably find scenarios where you need to write your own sort fields. Our sort field classes are designed to be easy to construct.
To generate a sort field, use the jsonapi:sort-field
Artisan command as follows:
php artisan jsonapi:sort-field CustomSort
This will generate the App\JsonApi\Sorting\CustomSort
class:
namespace App\JsonApi\Sorting;
use LaravelJsonApi\Eloquent\Contracts\SortField;
class CustomSort implements SortField
{
/**
* @var string
*/
private string $name;
/**
* Create a new sort field.
*
* @param string $name
* @param string|null $column
* @return CustomSort
*/
public static function make(string $name): self
{
return new static($name);
}
/**
* CustomSort constructor.
*
* @param string $name
*/
public function __construct(string $name)
{
$this->name = $name;
}
/**
* Get the name of the sort field.
*
* @return string
*/
public function sortField(): string
{
return $this->name;
}
/**
* Apply the sort order to the query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $direction
* @return \Illuminate\Database\Eloquent\Builder
*/
public function sort($query, string $direction = 'asc')
{
// @TODO
}
}
All you need to do now is update the make()
method and constructor if there are any other variables your class needs. Then add your query logic to the sort
method. This method receives the query builder instance and the direction provided by the client. The direction will either be asc
(ascending) or desc
(descending).
Once you have written the sort logic for your sort field, you can then use it in your schemas:
use App\JsonApi\Sorting\CustomSort;
CustomSort::make('name')