When implementing a JSON API, you may be required to modify Laravel's standard JSON API Resource meta section, in particular pagination.
Laravel's default JSON pagination looks like this:
"links":{
"first": "http://example.com/pagination?page=1",
"last": "http://example.com/pagination?page=1",
"prev": null,
"next": null
},
"meta":{
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "http://example.com/pagination",
"per_page": 15,
"to": 10,
"total": 10
}
However, there are no standards for implementation of JSON pagination. As an example, the JSON:API website simply has a totalPages field in the meta and a separate links section:
{
"meta": {
"totalPages": 13
},
"data": [...],
"links": {
"self": "http://example.com/articles?page[number]=3&page[size]=1",
"first": "http://example.com/articles?page[number]=1&page[size]=1",
"prev": "http://example.com/articles?page[number]=2&page[size]=1",
"next": "http://example.com/articles?page[number]=4&page[size]=1",
"last": "http://example.com/articles?page[number]=13&page[size]=1"
}
}
The JSON:API website further states:
Note: Putting a property like "totalPages" in "meta" can be a convenient way to indicate to clients the total number of pages in a collection (as opposed to the "last" link, which simply gives the URI of the last page). However, all "meta" values are implementation-specific, so you can call this member whatever you like ("total", "count", etc.) or not use it at all.
Those updating older Laravel projects that used Fractal might need to modify Laravel's default meta to act similarly to Fractal's adapter for the LengthAwarePaginator, which includes a "pagination" section within meta:
"meta": {
"pagination": {
"total": 50,
"count": 10,
"per_page": 10,
"current_page": 1,
"total_pages": 5,
"links": {
"next": "http://test.test/users?page=2"
}
}
}
For those updating a laravel-json-api project, you may need to change meta to look similar to this:
{
"meta": {
"page": {
"current-page": 2,
"per-page": 15,
"from": 16,
"to": 30,
"total": 50,
"last-page": 4
}
},
"links": {
"first": "http://localhost/api/v1/posts?page[number]=1&page[size]=15",
"prev": "http://localhost/api/v1/posts?page[number]=1&page[size]=15",
"next": "http://localhost/api/v1/posts?page[number]=3&page[size]=15",
"last": "http://localhost/api/v1/posts?page[number]=4&page[size]=15"
},
"data": [...]
}
The following is an example of subclassing PaginatedResourceResponse, AnonymousResourceCollection, and JsonResource in order to modify the JSON meta section.
All JSON Resources should extend this class.
<?php
namespace App\Api\V1\Resources\Json;
use Illuminate\Http\Resources\Json\JsonResource;
class CustomJsonResource extends JsonResource
{
public function toResponse($request)
{
return $this->resource instanceof AbstractPaginator
? (new CustomPaginatedResourceResponse($this))->toResponse($request)
: parent::toResponse($request);
}
public static function collection($resource)
{
return tap(new CustomAnonymousResourceCollection($resource, static::class), function ($collection) {
if (property_exists(static::class, 'preserveKeys')) {
$collection->preserveKeys = (new static([]))->preserveKeys === true;
}
});
}
}
<?php
namespace App\Api\V1\Resources\Json;
use Illuminate\Pagination\AbstractPaginator;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
class CustomAnonymousResourceCollection extends AnonymousResourceCollection
{
public function toResponse($request)
{
return $this->resource instanceof AbstractPaginator
? (new CustomPaginatedResourceResponse($this))->toResponse($request)
: parent::toResponse($request);
}
}
In this class you can customize the meta section of your JSON response. This specific example updates the meta section to behave similarly to default Fractal behaviour, including a "pagination" section within meta:
<?php
namespace App\Api\V1\Resources\Json;
use Illuminate\Http\Resources\Json\PaginatedResourceResponse;
use Illuminate\Support\Arr;
class CustomPaginatedResourceResponse extends PaginatedResourceResponse
{
protected function paginationLinks($paginated)
{
return [
'first' => $paginated['first_page_url'] ?? null,
'last' => $paginated['last_page_url'] ?? null,
'prev' => $paginated['prev_page_url'] ?? null,
'next' => $paginated['next_page_url'] ?? null,
];
}
protected function meta($paginated)
{
$meta = ['pagination' => Arr::except($paginated, [
'data',
'last_page_url',
'prev_page_url',
'next_page_url',
])];
$meta['pagination']['total_pages'] = $paginated['last_page'];
return $meta;
}
}
Excellent work!