juampi92/api-resources
juampi92/api-resources extends Laravel's Eloquent API resources with query-string controls for sparse fieldsets, nested includes, and filtering, letting clients request exactly the data they need while you keep resource definitions clean, consistent, and reusable.
The package (juampi92/api-resources) is a thin, opinionated layer on top of Laravel's JsonResource. You keep writing toArray($request) as usual, but gain whitelist-based field selection and strict lazy-loading protection. v1.10.0 is a compatibility/maintenance release: it adds Laravel 11.x support and drops --ignore-platform-reqs from the package's own composer scripts. There are no breaking changes, no new public API, and no deprecations — upgrading from 1.9.x is a straight composer update juampi92/api-resources.
Minimal setup:
composer require juampi92/api-resourcesphp artisan vendor:publish --tag=api-resources-config (verify the exact tag with php artisan vendor:publish if it differs). This is where the strictness knobs live — lazy-loading prevention, unknown-field handling, and resource wrapping.Juampi92\ApiResources\ApiResource (and its collection counterpart) instead of Illuminate\Http\Resources\Json\JsonResource.return UserResource::make($user)->only(['id', 'name']);
// or, for collections
return UserResource::collection($users)->only(['id', 'name']);
Where to look first: the published config file (it tells you how strict the package is out of the box), then the package README's only() / conditional-field semantics — that is the one concept that differs from stock Laravel resources.
First realistic use case: an existing endpoint that returns too much data or silently N+1s. Swap the resource base class, add ->only([...]) at the call site, and let the package surface any relation you forgot to eager load.
Two entry points per resource. The resource class defines the full field universe; the call site (controller or a parent resource) narrows it with only(). Treat toArray() as a schema and only() as the query projection — this keeps a single resource reusable across a mobile endpoint (3 fields) and an admin endpoint (15 fields).
Wrap conditionally-expensive fields in the package's inclusion helper (e.g. whenIncluded(...)) rather than when() / whenNotNull(). The field is only evaluated — and therefore only costs you a query or an accessor call — when the caller explicitly asked for it. Pair it with whenLoaded() for relations so the eager load and the field gate stay in sync:
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->whenIncluded($this->email),
'posts' => PostResource::collection($this->whenLoaded('posts')),
];
}
Nest explicitly, project explicitly. Field selection does not implicitly cascade. If UserResource embeds PostResource::collection(...), the child resource has its own field set — apply only() there too (->only(['id', 'title'])), otherwise the nested payload is whatever the child's toArray() returns. This is the single biggest source of "why is my response still huge" confusion.
Controller-level convention. Standardise on returning Resource::make($model)->only($request->validated('fields') ?? self::DEFAULT_FIELDS) for endpoints with a ?fields= parameter, and pass the resulting array through ->fields()/config introspection when you need to advertise the schema (e.g. OpenAPI generation).
Incremental migration. You don't need to convert everything. Because the base class extends Laravel's JsonResource, a resource can extend ApiResource and still be returned from any existing controller. Convert high-traffic endpoints first — those are where over-fetching and N+1s actually hurt.
Feature tests. Prefer ->resolve() (or ->response()->getData(true)) in tests so assertions run against the array, not a JsonResponse. Assert both the presence of whitelisted keys and the absence of everything else — that's the behaviour this package exists to guarantee.
only() on an undefined field is a failure mode, not a no-op. Depending on the unknown-field config, selecting a key that toArray() never returns will either throw or silently drop the projection. Keep field names in a class constant (const FIELDS = ['id', 'name', 'email'];) and reuse it at call sites and in tests.with() omissions. If a resource starts throwing after an unrelated refactor, the cause is usually a model accessor or an $appends attribute touching a relation. Audit $appends before disabling the guard.JsonResource. If you need it locally, scope it in tests only.config:cache; changing strictness requires a redeploy/recache, not just a file edit. This bites people debugging an environment-specific difference between local and prod.data wrapping, JsonResource::withoutWrapping(), and pagination envelopes behave exactly as in core — check middleware/AppServiceProvider first when the shape surprises you, before blaming the resource.ResourceCollection or a AnonymousResourceCollection built from the base JsonResource will bypass the field-projection logic. Use Resource::collection($items) off an ApiResource subclass.^1.10; on Laravel 10 or below, anything ^1.9 remains fine. The removed --ignore-platform-reqs only affects the package's own CI scripts, not consumers.created_at to every resource), prefer a base class in your app over patching vendor config — the package's surface area is small and deliberately unopinionated about your domain.How can I help you explore Laravel packages today?