Sorting

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 returned in responses.

This package provides the following capabilities:

  • Validation of sort parameters sent by a client.
  • Automatic conversion of sort parameters to query builder ordering for Eloquent resources.

Example Requests

Sorting is applied when:

  • Fetching resources, e.g. GET /api/posts.
  • Fetching related resources, e.g. GET /api/countries/1/posts.
  • Fetching relationship identifiers, e.g. GET /api/countries/1/relationships/posts.

As an example, imagine our posts resource has title and createdAt sort parameters.

This request would return posts with the most recently created first (descending order):

GET /api/posts?sort=-createdAt HTTP/1.1
Accept: application/vnd.api+json

This request would return posts that are related to country 1, sorted by title ascending, then createdAt ascending:

GET /api/countries/1/posts?sort=title,createdAt HTTP/1.1
Accept: application/vnd.api+json

This request would return the resource identifiers of any post that is related to country 1, sorted by the post's createdAt attribute in ascending order:

GET /api/countries/1/relationships/posts?sort=createdAt HTTP/1.1
Accept: application/vnd.api+json

Allowing Sort Parameters

By default validators generated by this package do not allow any sort parameters. This is because the Eloquent adapter automatically converts these parameters to column names for query builder ordering. We therefore expect sort parameters to be whitelisted otherwise the client could provide a path that is not a valid column name.

To allow sort parameters, list them on your resource's validators class using the $allowedSortParameters property:

namespace App\JsonApi\Posts;

use CloudCreativity\LaravelJsonApi\Validation\AbstractValidators;

class Validators extends AbstractValidators
{

    protected $allowedSortParameters = [
        'createdAt',
        'title',
    ];

    // ...
}

You do not need to list the ascending and descending variations of the sort parameter. For example, if you allow createdAt, then we will allow the client to send both createdAt and -createdAt.

If the client provides an invalid sort parameter, it will receive the following response:

HTTP/1.1 400 Bad Request
Content-Type: application/vnd.api+json

{
    "errors": [
        {
            "title": "Invalid Query Parameter",
            "status": "400",
            "detail": "Sort parameter foo is not allowed.",
            "source": {
                "parameter": "sort"
            }
        }
    ]
}

Default Sort Order

A default sort order can be set on an Eloquent adapter. This will be used when a client provides no sort parameters. For example, if we wanted our posts to be returned in descending created date by default:

class Adapter extends AbstractAdapter
{

    protected $defaultSort = '-createdAt';

}

Note that the sort order is the JSON API sort parameter, not the Eloquent column name.

You can also set multiple parameters as the default sort order:

class Adapter extends AbstractAdapter
{

    protected $defaultSort = ['-createdAt', 'title'];

}

Implementing Sorting

The Eloquent adapter automatically converts sort parameters into column names, and then applies these to the query builder instance used when fetching resources or relationships. To do this, we convert the sort parameter to the snake case form of the name, and then use either orderBy method on the query builder to apply the sort order (either ascending or descending).

If you have a JSON API sort parameter that does not convert directly to a column name, you can define the conversion on the $sortColumns property of your adapter. For example, if the title sort parameter has to be applied to the main_title column:

class Adapter extends AbstractAdapter
{

    protected $sortColumns = [
        'title' => 'main_title',
    ];

}

To implement more complex sorting where you need access to the Eloquent query builder instance, you can add methods to your adapter. The method name must follow the pattern sortBy<Name>, where <Name> is the camel cased name of the JSON API sort field.

For example, if you had a JSON API sort field called likes, you could implement the sortByLikes method on your adapter. This will receive the query builder instance and the direction of the sort (either asc or desc).

class Adapter extends AbstractAdapter
{
    // ...

    protected function sortByLikes($query, $direction): void
    {
        $query->withCount('likes')
            ->orderBy('likes_count', $direction);
    }

}