Returning an Eloquent model directly from a route auto-converts it to JSON, but that default output mirrors your database columns exactly — API Resources are Laravel's standard, framework-idiomatic tool for controlling exactly what shape a JSON response actually takes.
The default, unshaped response
Route::get('/users/{user}', fn (User $user) => $user);
{"id": 1, "name": "Alex", "email": "alex@example.com", "password": "$2y$10$...", "created_at": "...", "updated_at": "..."}
This default output includes every column, including ones (like password, even if hashed) that should genuinely never be exposed in an API response — a real security concern, not just a cosmetic one.
Creating an API Resource
php artisan make:resource UserResource
class UserResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'member_since' => $this->created_at->format('Y-m-d'),
];
}
}
Route::get('/users/{user}', fn (User $user) => new UserResource($user));
{"data": {"id": 1, "name": "Alex", "email": "alex@example.com", "member_since": "2022-03-15"}}
The resource explicitly controls exactly which fields appear, renames created_at to a friendlier member_since, and formats the date — while the sensitive password field is simply never mentioned, so it never appears at all.
Why the response is wrapped in a "data" key
Laravel wraps a single resource's output in a top-level data key by default — a convention that leaves room to add metadata (pagination info, for instance) alongside the actual resource data without a naming conflict, and matches a common API response convention many client libraries expect.
Resource collections, for a list of many models
Route::get('/users', fn () => UserResource::collection(User::all()));
{"data": [{"id": 1, "name": "Alex", ...}, {"id": 2, "name": "Sam", ...}]}
Adding conditional fields to the response
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->when($request->user()?->is_admin, $this->email),
];
}
$this->when() includes a field in the output only when the given condition is true — here, only an admin viewing the response sees the email address at all, which is a genuinely different (and stronger) approach than including the field and relying on the front end to simply not display it.
Adding extra top-level metadata alongside the data
public function with($request): array
{
return [
'meta' => [
'api_version' => '1.0',
],
];
}
{"data": {...}, "meta": {"api_version": "1.0"}}
Wrapping every response in a consistent envelope globally
// AppServiceProvider::boot()
JsonResource::withoutWrapping(); // to REMOVE the default "data" wrapper, if a different convention is preferred
For a project wanting a different response envelope convention entirely, withoutWrapping() removes Laravel's default data key wrapping globally, letting each resource's toArray() define the complete top-level response structure itself instead.