zhylon / api-resources
Opinionated helpers for building consistent, JSON:API-inspired API resource responses in Laravel.
Requires
- php: ^8.2
- illuminate/contracts: ^12.0 || ^13.0
- illuminate/http: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
Requires (Dev)
- laravel/pint: ^1.30
- orchestra/testbench: ^10.0 || ^11.0
- pestphp/pest: ^3.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Small, opinionated helpers for building consistent, JSON:API-inspired API resource responses in Laravel:
BuildsApiResource— a trait forJsonResourceclasses that produces a{ id, type, attributes, links }payload, and makesYourResource::collection(...)return an enveloped, paginated collection automatically.ApiResourceCollection— theAnonymousResourceCollectionsubclass behind that: wraps items indataand addslinks/metapagination info for both length-aware and cursor paginators.
Requirements
- PHP 8.2+
- Laravel 12.x or 13.x
Installation
composer require zhylon/api-resources
There is nothing to publish or configure — both classes are used directly.
Usage
Single resources — BuildsApiResource
Add the trait to a JsonResource and call toApiResource() from toArray():
use Illuminate\Http\Resources\Json\JsonResource; use Zhylon\ApiResources\Http\Resources\BuildsApiResource; class OrderResource extends JsonResource { use BuildsApiResource; public function toArray($request): array { return $this->toApiResource( type: 'order', attributes: [ 'status' => $this->status, 'total' => $this->total, ], selfHref: route('orders.show', $this->resource), timestamps: true, ); } }
produces:
{
"id": "abc123",
"type": "order",
"attributes": {
"status": "paid",
"total": 42.0,
"created_at": "2026-01-01T00:00:00+00:00",
"updated_at": "2026-01-01T00:00:00+00:00"
},
"links": {
"self": { "href": "https://example.test/orders/abc123" }
}
}
toApiResource() accepts:
| Parameter | Type | Description |
|---|---|---|
type |
string |
The JSON:API-style resource type, e.g. 'order' |
attributes |
array |
The resource's own attributes |
selfHref |
?string |
Value for links.self.href |
timestamps |
bool |
Appends created_at/updated_at (as ISO-8601 strings) to attributes |
id |
string|int|null |
Overrides automatic id resolution (see below) |
Resolving the id
When id isn't passed explicitly, resolveResourceId() resolves it in this order:
safeHashID()on the underlying resource, if it defines one.hashID(), if the underlying resource implementsTobyMaxham\HashId\Interfaces\Hashing.- The resource's primary key (
getKey()), or itsidproperty as a last resort.
None of this requires tobymaxham/laravel-hashid (or any other hashid package) to be installed — the checks use method_exists()/interface_exists(), so they simply no-op down the chain when unavailable.
For full custom control, override resolveResourceId() in your resource class:
use Illuminate\Http\Resources\Json\JsonResource; use Zhylon\ApiResources\Http\Resources\BuildsApiResource; class OrderResource extends JsonResource { use BuildsApiResource; protected function resolveResourceId(): string|int|null { return $this->resource->uuid; } // ... }
Collections — ApiResourceCollection
Because BuildsApiResource overrides the static collection() method Laravel already gives every JsonResource, you don't need a dedicated collection class — just call it on your resource:
return OrderResource::collection(Order::paginate());
This returns an ApiResourceCollection (an AnonymousResourceCollection under the hood) that wraps the items in a data key and — exactly like Laravel's own paginated resource responses — adds links/meta automatically for both Illuminate\Pagination\LengthAwarePaginator and cursor paginators (Illuminate\Contracts\Pagination\CursorPaginator). Passing a plain collection instead of a paginator just returns { "data": [...] }, no extra setup required.
If you need a dedicated collection class (e.g. to add extra top-level meta), extend ApiResourceCollection directly and pass both the resource and the item class it collects into:
use Zhylon\ApiResources\Http\Resources\ApiResourceCollection; class OrderCollection extends ApiResourceCollection { public function __construct($resource) { parent::__construct($resource, OrderResource::class); } }
return new OrderCollection(Order::paginate());
Testing
composer test
License
The MIT License (MIT). See LICENSE for more information.