Using Laravel Resource Classes to Transform Eloquent Models into JSON API Responses

This article explains how to generate and customize Laravel resource and resource collection classes, use them to convert Eloquent models and collections into JSON, handle pagination, conditional attributes, relationships, and add metadata to API responses.

Laravel Tech Community
Laravel Tech Community
Laravel Tech Community
Using Laravel Resource Classes to Transform Eloquent Models into JSON API Responses

Generating Resources

When building an API you often need a transformation layer that connects your Eloquent models to the JSON responses returned to clients. Laravel's resource classes provide an intuitive way to convert models and model collections into JSON.

You can generate a resource class using the php artisan make:resource UserResource command. By default the generated class is placed in the app/Http/Resources folder and extends Illuminate\Http\Resources\Json\JsonResource.

php artisan make:resource UserResource

Resource Collections

Besides generating resources for a single model, you can generate a resource collection to transform a set of models. This allows you to include links and other meta information related to the resource in the response.

When generating the resource you need to add the --collection flag, or include the word Collection in the class name to indicate that a collection should be generated. The collection class extends Illuminate\Http\Resources\Json\ResourceCollection.

php artisan make:resource Users --collection

php artisan make:resource UserCollection

Concept Overview

Before customizing how you write your resources, let’s look at how resources are used in Laravel. A resource class represents a single model that needs to be transformed into JSON. For example, a simple UserResource class might look like this:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'id'         => $this->id,
            'name'       => $this->name,
            'email'      => $this->email,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ];
    }
}

Each resource class defines a toArray method that returns the array of attributes that should be sent to the client. You can return the defined resource from a route or controller like this:

use App\User;
use App\Http\Resources\UserResource;

Route::get('/user', function () {
    return new UserResource(User::find(1));
});

Resource Collections

You can use the collection method on a resource to create a collection of resources, returning multiple resources or a paginated response:

use App\User;
use App\Http\Resources\UserResource;

Route::get('/user', function () {
    return UserResource::collection(User::all());
});

By default the top‑level resource is wrapped in a data key. If you need custom metadata you can define a dedicated collection class: php artisan make:resource UserCollection Inside the generated collection you can add any metadata you wish to return:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
    /**
     * Transform the resource collection into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'data'  => $this->collection,
            'links' => [
                'self' => 'link-value',
            ],
        ];
    }
}

You can return the collection from a route or controller:

use App\User;
use App\Http\Resources\UserCollection;

Route::get('/users', function () {
    return new UserCollection(User::all());
});

Data Wrapping

By default, when a resource response is converted to JSON the top‑level resource is wrapped in a data key. A typical resource collection response looks like:

{
    "data": [
        {"id":1,"name":"Eladio Schroeder Sr.","email":"[email protected]"},
        {"id":2,"name":"Liliana Mayert","email":"[email protected]"}
    ]
}

You can disable this top‑level wrapping by calling the withoutWrapping method on the base Resource class, typically from a service provider such as AppServiceProvider:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Http\Resources\Json\Resource;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Resource::withoutWrapping();
    }

    public function register()
    {
        //
    }
}
{tip} The withoutWrapping method only disables the top‑level data key; any manually added data keys inside a collection remain untouched.

Nested Resource Wrapping

You can decide how nested resources are wrapped. If you want every nested collection to be wrapped in a data key, define a collection class for each resource and wrap the returned collection in a data key. Laravel will never double‑wrap a resource.

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class CommentsCollection extends ResourceCollection
{
    public function toArray($request)
    {
        return ['data' => $this->collection];
    }
}

Data Wrapping and Pagination

When returning a paginated collection, even if you called withoutWrapping, Laravel will still wrap the data in a data key and include meta and links for pagination information:

{
    "data": [
        {"id":1,"name":"Eladio Schroeder Sr.","email":"[email protected]"},
        {"id":2,"name":"Liliana Mayert","email":"[email protected]"}
    ],
    "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}
}

You can pass a paginator instance to the collection method or to a custom collection class:

use App\User;
use App\Http\Resources\UserCollection;

Route::get('/users', function () {
    return new UserCollection(User::paginate());
});

Conditional Attributes

Sometimes you want to add an attribute only when a certain condition is met, for example only for administrators. Laravel provides the when method to conditionally add attributes:

public function toArray($request)
{
    return [
        'id'    => $this->id,
        'name'  => $this->name,
        'email' => $this->email,
        'secret'=> $this->when($this->isAdmin(), 'secret-value'),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}

The when method also accepts a closure as its second argument, which is only evaluated when the condition is true:

'secret' => $this->when($this->isAdmin(), function () {
    return 'secret-value';
}),
{note} The method called on the resource is proxied to the underlying model, so isAdmin() is actually executed on the original Eloquent model.

Conditional Merging of Data

If you need to add multiple attributes when a condition is true, use the mergeWhen method:

public function toArray($request)
{
    return [
        'id'    => $this->id,
        'name'  => $this->name,
        'email' => $this->email,
        $this->mergeWhen($this->isAdmin(), [
            'first-secret'  => 'value',
            'second-secret' => 'value',
        ]),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}
{note} mergeWhen should not be used on arrays with mixed string and numeric keys or on non‑sequential numeric keys.

Conditional Relationships

You can also conditionally include relationships based on whether they have been loaded, using the whenLoaded method. This helps avoid N+1 query problems:

public function toArray($request)
{
    return [
        'id'    => $this->id,
        'name'  => $this->name,
        'email' => $this->email,
        'posts' => PostResource::collection($this->whenLoaded('posts')),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}

If the posts relationship is not loaded, the posts key will be omitted from the response.

Conditional Pivot Data

For many‑to‑many relationships you can conditionally include pivot table data using whenPivotLoaded:

public function toArray($request)
{
    return [
        'id'      => $this->id,
        'name'    => $this->name,
        'expires_at' => $this->whenPivotLoaded('role_users', function () {
            return $this->pivot->expires_at;
        }),
    ];
}

Adding Metadata

Some JSON API specifications require additional metadata such as links or other information. You can simply add these keys inside the toArray method. When adding metadata to a collection, Laravel will merge your custom links with the pagination links automatically.

public function toArray($request)
{
    return [
        'data'  => $this->collection,
        'links' => [
            'self' => 'link-value',
        ],
    ];
}

Top‑Level Metadata

If you need to add metadata that appears only when the resource is the top‑level response, define a with method that returns an array of additional data:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
    public function toArray($request)
    {
        return parent::toArray($request);
    }

    public function with($request)
    {
        return [
            'meta' => [
                'key' => 'value',
            ],
        ];
    }
}

Adding Metadata When Constructing Resources

You can also attach top‑level data when you instantiate a resource using the additional method:

return (new UserCollection(User::all()->load('roles')))
    ->additional([
        'meta' => [
            'key' => 'value',
        ],
    ]);

Resource Responses

Resources can be returned directly from routes or controllers:

use App\User;
use App\Http\Resources\UserResource;

Route::get('/user', function () {
    return new UserResource(User::find(1));
});

If you need to customize the HTTP response, you can chain the response method on the resource and then modify headers:

return (new UserResource(User::find(1)))
    ->response()
    ->header('X-Value', 'True');

Alternatively, define a withResponse method inside the resource class. This method receives the request and the response instance, allowing you to set headers or other response attributes:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
        ];
    }

    public function withResponse($request, $response)
    {
        $response->header('X-Value', 'True');
    }
}
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendJSONPHPAPIresourceLaravel
Laravel Tech Community
Written by

Laravel Tech Community

Specializing in Laravel development, we continuously publish fresh content and grow alongside the elegant, stable Laravel framework.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.