In Laravel, when do you write a ResourceCollection class instead of calling WorkoutResource::collection(), and how do with() and additional() add top-level meta?
answer
- anonymous versus named collection
- make:resource WorkoutCollection or --collection
- $this->collection holds mapped resources
- with() only on the outermost resource
- additional() set at the call site
basics
~20 sWorkoutResource::collection() builds an anonymous collection that maps each item and is enough for plain lists and pages. Write a ResourceCollection when the list itself needs fields, meta or pagination hooks; with() adds class-defined meta, additional() adds per-call meta.
solid answer
~40 s`WorkoutResource::collection($workouts)` returns an `AnonymousResourceCollection` that maps each model through `WorkoutResource`, and it handles paginators too. A named `WorkoutCollection extends ResourceCollection` (generated by `make:resource WorkoutCollection` or `--collection`) is worth writing when the collection has its own output: summary fields such as weekly totals, a `with()` method, `paginationInformation()`, or reuse across endpoints. It finds its item class by stripping `Collection` from its name, or from `#[Collects]` or `$collects`, and exposes the mapped items as `$this->collection`. `with($request)` lives in the class and is merged only when that resource is the outermost response; `additional([...])` is chained at the call site for request-specific meta. Both are merged recursively beside the `data` key.
code
php · 23 lines<?php
namespace App\Http\Controllers;
use App\Http\Resources\WorkoutCollection;
use App\Http\Resources\WorkoutResource;
use App\Models\Workout;
use Illuminate\Http\Request;
class WeeklyWorkoutController
{
public function index(Request $request)
{
$week = $request->user()->workouts()->thisWeek()->get();
// Plain list: the anonymous collection is enough.
// return WorkoutResource::collection($week);
// List with its own fields and meta: a named collection.
return (new WorkoutCollection($week))
->additional(['meta' => ['week' => now()->isoWeek()]]);
}
}go deeper
Recall WorkoutResource::collection() for lists and make:resource WorkoutCollection for a named collection whose toArray() can add list-level fields.
Explain how a ResourceCollection resolves the item class, what $this->collection holds, the difference between with() and additional(), and the 201 rule for freshly created models.
Choose between anonymous and named collections deliberately, keep response meta on the outermost resource, and predict how custom meta merges with pagination output.
Standardise list envelopes across an API: which meta every list carries, where it is defined, and how named collections stay consistent as endpoints multiply.
## Two ways to render a list An API resource (a class extending `JsonResource`) shapes one model. For a list there are two options. 1. **The anonymous collection.** `WorkoutResource::collection($workouts)` returns an `AnonymousResourceCollection`, a `ResourceCollection` that remembers which resource to apply and maps every item through it. It accepts a collection or a paginator, so `WorkoutResource::collection(Workout::paginate(20))` already produces `data`, `links` and `meta`. In Laravel 13, `$workouts->toResourceCollection()` and `$paginator->toResourceCollection()` do the same by convention. 2. **A named collection.** `php artisan make:resource WorkoutCollection` (the `Collection` suffix, or the `--collection` flag, selects the collection stub) creates a class extending `ResourceCollection`. You return it with `new WorkoutCollection($workouts)`. ## When a named collection earns its place The anonymous collection has no code of its own, so write a class when the **list** needs behaviour: - **List-level fields**: a fitness app's weekly screen wants `total_km` and `sessions` beside the workouts. - **A `with()` method** that always adds the same meta, such as the units system. - **`paginationInformation()`** to reshape `links` and `meta`, which the paginated response looks for on the collection instance. - **Reuse**: several endpoints return the same list shape. - **Convention discovery**: among its naming conventions, `toResourceCollection()` prefers a `WorkoutCollection` class over `WorkoutResource::collection()`, and `#[UseResourceCollection]` on the model names one explicitly. If none of these apply, the anonymous collection is simpler and adds no class to maintain. ## How a ResourceCollection maps its items The constructor stores the given items and maps each into the **collected resource class**, exposing the result as `$this->collection`. That class is resolved in this order: 1. a `#[Collects(WorkoutResource::class)]` attribute on the collection; 2. the public `$collects` property; 3. the collection's own name with `Collection` removed (`App\Http\Resources\Workout`), then with `Collection` replaced by `Resource` (`App\Http\Resources\WorkoutResource`). If the resolved class is not a `JsonResource`, a `LogicException` is thrown. Items that are already instances of the collected class are not wrapped a second time. When the input is a paginator, the mapped items are put back into it with `setCollection()`, so the page state behind `links` and `meta` survives. The collection is also countable and iterable, so `count()` and `foreach` work on it directly. In `toArray()` you usually return `parent::toArray($request)` or an array such as `['data' => $this->collection, 'total_km' => ...]`; a `data` key you write yourself is kept as is. By default the item keys are renumbered; the `#[PreserveKeys]` attribute on the item resource keeps them, for example when the list is keyed by date. ## with() versus additional() Both add keys beside the payload at the top level of the JSON, and both are merged with `array_merge_recursive`, together with pagination `links` and `meta` when the collection wraps a paginator. | | `with(Request $request)` | `additional(array $data)` | |---|---|---| | Defined | as a method in the resource class | chained where the resource is created | | Varies by | anything the class can compute | anything the controller knows | | Applied when | the resource is the outermost response | the resource is the outermost response | | Typical use | API version, units system | request-specific counts, feature flags | Neither applies to a resource nested inside another resource's array: only the outermost resource builds the response. If you add a `meta` key through either and the collection is paginated, your entries sit beside `current_page` and `total` rather than replacing them. ## Customising the response itself Two more hooks complete the picture: - `->response()` turns any resource into an `Illuminate\Http\JsonResponse`, so you can chain `->header('X-Units', 'metric')` or `->setStatusCode(...)`. - A `withResponse(Request $request, JsonResponse $response)` method on the class runs whenever that resource is the outermost response. The status defaults to 200, except that a single resource wrapping a model whose `wasRecentlyCreated` flag is true, such as one just returned from `Workout::create()`, is sent with **201 Created**. ## Example ```php <?php namespace App\Http\Resources; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\ResourceCollection; class WorkoutCollection extends ResourceCollection { public function toArray(Request $request): array { return [ 'data' => $this->collection, 'total_km' => round($this->collection->sum('distance_m') / 1000, 1), ]; } public function with(Request $request): array { return ['meta' => ['units' => 'metric']]; } } ``` A controller can then add request-specific meta: `(new WorkoutCollection($week))->additional(['meta' => ['week' => $isoWeek]])`, and both `meta` arrays merge into one.
- What status code does returning new WorkoutResource(Workout::create($data)) send, and why?201 Created. The resource response checks whether the wrapped value is an Eloquent model whose `wasRecentlyCreated` flag is true, which `create()` sets after inserting. Any other single resource, and collections, default to 200. To send something else, chain `->response()->setStatusCode(...)`.
- Why is with() on a nested ExerciseResource never seen in the JSON?`with()` is read only by the response builder, which runs for the resource returned from the controller. A resource nested in another resource's array is serialized through `jsonSerialize()`, which resolves its `toArray()` alone. Put list- or response-level meta on the outermost resource or pass it with `additional()`.
- How does a named collection decide which resource to map each item into?It uses a `#[Collects]` attribute if present, else the `$collects` property, else its own class name with `Collection` stripped (`Workout`) or replaced by `Resource` (`WorkoutResource`) in the same namespace. A resolved class that is not a `JsonResource` raises a `LogicException`; if nothing resolves, items are output through their own `toArray()`.
saying these in an interview costs you the question
- Every model needs its own ResourceCollection class to return a list.
- WorkoutResource::collection() cannot handle a paginator.
- with() meta also appears for every nested resource.
- additional() replaces the pagination meta instead of merging with it.
- Resource responses always use status 200, even after create().