Skip to content

Instantly share code, notes, and snippets.

@derekphilipau
Last active March 25, 2021 07:31
Show Gist options
  • Save derekphilipau/4be52164a69ce487dcd0673656d280da to your computer and use it in GitHub Desktop.
Save derekphilipau/4be52164a69ce487dcd0673656d280da to your computer and use it in GitHub Desktop.

Modifying Laravel's default JSON API Resources meta section

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": [...]
}

Modifying meta section in paginated JSON API Resources

The following is an example of subclassing PaginatedResourceResponse, AnonymousResourceCollection, and JsonResource in order to modify the JSON meta section.

CustomJsonResource.php

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;
            }
        });
    }
}

CustomAnonymousResourceCollection.php

<?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);
    }
}

CustomPaginatedResourceResponse

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;
    }
}
@emjayess
Copy link

Excellent work!

@ametad
Copy link

ametad commented Mar 20, 2020

This helped me a lot, thank you @derekphilipau !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment