---
description: >-
  https://www.digitalocean.com/community/tutorials/how-to-create-laravel-eloquent-api-resources-to-convert-models-into-json
---

# How to Create Laravel Eloquent API Resources to Convert Models into JSON

## How to Create Laravel Eloquent API Resources to Convert Models into JSON

Published on December 12, 2019

* [Databases](https://www.digitalocean.com/community/tags/databases)
* [PHP](https://www.digitalocean.com/community/tags/php)
* [PHP Frameworks](https://www.digitalocean.com/community/tags/php-frameworks)
* [Laravel](https://www.digitalocean.com/community/tags/laravel)

#### Introduction <a href="#introduction" id="introduction"></a>

When creating APIs, we often need to work with database results to filter, interpret or format values that will be returned in the API response. [API resource classes](https://laravel.com/docs/5.6/eloquent-resources) allow you to convert your models and model collections into JSON, working as a data transformation layer between the database and the controllers.

API resources provide a uniform interface that can be used anywhere in the application. Eloquent relationships are also taken care of.

[Laravel](https://laravel.com/) provides two [`artisan`](https://laravel.com/docs/5.6/artisan) commands for generating _resources_ and _collections_ - we’ll understand the difference between these two later on. But for both resources and collections, we have our response wrapped in a data attribute: a JSON response standard.

We’ll look at how to work with API resources in the next section by playing around with a demo project.

### Prerequisites <a href="#prerequisites" id="prerequisites"></a>

To follow along with this guide, you need to meet the following prerequisites:

* A working Laravel development environment. To set this up, you can follow our guide on [How to Install and Configure a Laravel application on Ubuntu 18.04](https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-laravel-with-lemp-on-ubuntu-18-04).

This tutorial was written with PHP v7.1.3 and Laravel v5.6.35.

This tutorial was verified with PHP v7.3.11, Composer v.1.10.7, MySQL 5.7.0, and Laravel v.5.6.35.

### Step 1 — Cloning the Starter <a href="#step-1-cloning-the-starter" id="step-1-cloning-the-starter"></a>

Clone [this repo](https://github.com/do-community/songs-demo) and follow the instructions in the `README.md` to get things up and running.

First, clone the repo:

```
git clone `git@github.com:do-community/songs-demo.git`
```

Copy

Then, navigate to the project folder:

```
cd songs-demo
```

Copy

Create a `.env` file by running the following command:

```
cp .env.example .env
```

Copy

Update your database credentials inside this `.env` file.

Install the packages and dependencies:

```
composer install
```

Copy

**Note:** You have to be inside your Laravel development environment for this to work. For those using Vagrant, make sure you `ssh` into Vagrant before running `composer install`.

Then, generate an encryption key for the app:

```
php artisan key:generate
```

Copy

Run migrations and seed database with some sample data:

```
php artisan migrate:refresh --seed
```

Copy

### Step 2 — Setting up the Project <a href="#step-2-setting-up-the-project" id="step-2-setting-up-the-project"></a>

With the project setup, we can now start getting our hands dirty. Also, since this is a small project, we won’t be creating any controllers and will instead test out responses inside route closures.

Let’s start by generating a `SongResource` class:

```
php artisan make:resource SongResource
```

Copy

Resource files usually go inside the `App\Http\Resources` folder.

Let’s peek inside the newly created resource file - SongResource:

app/Http/Resources/SongResource.php

```
[...]
class SongResource extends JsonResource
{
    /**
    * Transform the resource into an array.
    *
    *  @param  \Illuminate\Http\Request  $request
    *  @return array
    **/
    public function toArray($request)
    {
        return parent::toArray($request);
    }
}
```

Copy

By default, we have `parent::toArray($request)` inside the `toArray()` method. If we leave things at this, all visible model attributes will be part of our response. To tailor the response, we specify the attributes we want to be converted to JSON inside this `toArray()` method.

Let’s update the `toArray()` method to match the following snippet:

app/Http/Resources/SongResource.php

```
[...]
public function toArray($request)
{
    return [
        'id' => $this->id,
        'title' => $this->title,
        'rating' => $this->rating,
    ];
}
```

Copy

As you can see, we can access the model properties directly from the `$this` variable because a resource class automatically allows method access down to the underlying model.

Let’s now update the `routes/api.php` with the following snippet:

routes/api.php

```
[...]
use App\Http\Resources\SongResource;
use App\Song;
[...]

Route::get('/songs/{song}', function(Song $song) {
    return new SongResource($song);
});

Route::get('/songs', function() {
    return new SongResource(Song::all());
});
```

Copy

If we visit the URL `/api/songs/1`, we’ll see a JSON response containing the key-value pairs we specified in the `SongResource` class for the song with an id of `1`:

```
{
  data: {
    id: 1,
    title: "Mouse.",
    rating: 3
  }
}
```

Copy

However, if we try visiting the URL `/api/songs`, an Exception is thrown:

```
OutputProperty [id] does not exist on this collection instance.
```

This is because instantiating the SongResource class requires a resource instance to be passed to the constructor and not a collection. That’s why the exception is thrown.

If we wanted a collection returned instead of a single resource, there is a static `collection()` method that can be called on a Resource class passing in a collection as the argument. Let’s update our `/songs` route closure to this:

```
Route::get('/songs', function() {
    return SongResource::collection(Song::all());
});
```

Copy

Visiting the `/api/songs` URL again will give us a JSON response containing all the songs.

```
{
  "data": [
    {
      "id": 1,
      "title": "Mouse.",
      "rating": 3
    },
    {
      "id": 2,
      "title": "I'll.",
      "rating": 0
    }
  ]
}
```

Copy

Resources work just fine when returning a single resource or even a collection but have limitations if we want to include metadata in the response. That’s where `Collections` come to our rescue.

To generate a collection class, we run:

```
php artisan make:resource SongsCollection
```

Copy

The main difference between a JSON resource and a JSON collection is that a resource extends the `JsonResource` class and expects a single resource to be passed when being instantiated while a collection extends the `ResourceCollection` class and expects a collection as the argument when being instantiated.

Back to the metadata bit. Assuming we wanted some metadata such as the total song count to be part of the response, here’s how to go about it when working with the `ResourceCollection` class:

app/Http/Resources/SongsCollection.php

```
[...]
class SongsCollection extends ResourceCollection
{
    public function toArray($request)
    {
        return [
            'data' => $this->collection,
            'meta' => ['song_count' => $this->collection->count()],
        ];
    }
}
```

Copy

If we update our `/api/songs` route closure to this:

routes/api.php

```
[...]
use App\Http\Resources\SongsCollection;
[...]
Route::get('/songs', function() {
    return new SongsCollection(Song::all());
});
```

Copy

And visit the URL `/api/songs`, we now see all the songs inside the data attribute as well as the total count inside the meta bit:

```
{
  "data": [
    {
      "id": 1,
      "title": "Mouse.",
      "artist": "Carlos Streich",
      "rating": 3,
      "created_at": "2018-09-13 15:43:42",
      "updated_at": "2018-09-13 15:43:42"
    },
    {
      "id": 2,
      "title": "I'll.",
      "artist": "Kelton Nikolaus",
      "rating": 0,
      "created_at": "2018-09-13 15:43:42",
      "updated_at": "2018-09-13 15:43:42"
    },
    {
      "id": 3,
      "title": "Gryphon.",
      "artist": "Tristin Veum",
      "rating": 3,
      "created_at": "2018-09-13 15:43:42",
      "updated_at": "2018-09-13 15:43:42"
    }
  ],
  "meta": {
    "song_count": 3
  }
}
```

Copy

But we have a problem, each song inside the data attribute is not formatted to the specification we defined earlier inside the SongResource and instead has all attributes.

To fix this, inside the `toArray()` method, set the value of `data` to `SongResource::collection($this->collection)` instead of having `$this->collection`.

Our `toArray()` method will now look like this:

app/Http/Resources/SongsCollection.php

```
[...]
public function toArray($request)
{
    return [
        'data' => SongResource::collection($this->collection),
        'meta' => ['song_count' => $this->collection->count()]
    ];
}
```

Copy

You can verify we get the correct data in the response by visiting the `/api/songs` URL again.

What if one wants to add metadata to a single resource and not a collection? Luckily, the `JsonResource` class comes with an `additional()` method which lets you specify any additional data you’d like to be part of the response when working with a resource:

routes/api.php

```
[...]
Route::get('/songs/{song}', function(Song $song) {
    return (new SongResource(Song::find(1)))->additional([
        'meta' => [
            'anything' => 'Some Value'
        ]
    ]);
});
```

Copy

In this case, the response would look somewhat like this:

```
{
  "data": {
    "id": 1,
    "title": "Mouse.",
    "rating": 3
  },
  "meta": {
    "anything": "Some Value"
  }
}
```

Copy

### Step 3 — Creating Model Relationships <a href="#step-3-creating-model-relationships" id="step-3-creating-model-relationships"></a>

In this project, we only have two models, `Album` and `Song`. The current relationship is a `one-to-many` relationship, meaning an album has many songs and a song belongs to an album.

We’ll now update the `toArray()` method inside the SongResource class so that it references the album:

app/Http/Resources/SongResource.php

```
[...]
class SongResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            [...]
            // other attributes
            'album' => $this->album
        ];
    }
}
```

Copy

If we want to be more specific in terms of what album attributes will be present in the response, we can create an AlbumResource similar to what we did with songs.

To create the `AlbumResource`, run:

```
php artisan make:resource AlbumResource
```

Copy

Once the resource class has been created, we then specify the attributes we want to be included in the response.

app/Http/Resources/AlbumResource.php

```
[...]
class AlbumResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'title' => $this->title
        ];
    }
}
```

Copy

And now inside the `SongResource` class, instead of doing `'album' => $this->album`, we can make use of the `AlbumResource` class we just created.

app/Http/Resources/SongResource.php

```
[...]
class SongResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            [...]
            // other attributes
            'album' => new AlbumResource($this->album)
        ];
    }
}
```

Copy

If we visit the `/api/songs` URL again, you’ll notice an album will be part of the response. The only problem with this approach is that it brings up the `N + 1` query problem.

For demonstration purposes, add the following snippet inside the `routes/api.php` file:

routes/api.php

```
[...]
DB::listen(function($query) {
    var_dump($query->sql);
});
```

Copy

Visit the `/api/songs` URL again. Notice that for each song, we make an extra query to retrieve the album’s details? This can be avoided by eager-loading relationships. In our case, update the code inside the `/api/songs` route closure to:

routes/api.php

```
[...]
return new SongsCollection(Song::with('album')->get());
```

Copy

Reload the page again and you’ll notice the number of queries has reduced.

Comment out the `DB::listen` snippet since we don’t need that anymore.

### Step 4 — Using Conditionals when Working with Resources <a href="#step-4-using-conditionals-when-working-with-resources" id="step-4-using-conditionals-when-working-with-resources"></a>

Every now and then, we might have a conditional determining the type of response that will be returned.

One approach we could take is introducing if statements inside our `toArray()` method. The good news is we don’t have to do that as there is a `ConditionallyLoadsAttributes` trait required inside the JsonResource class that has a handful of methods for handling conditionals.

We’ll only discuss the `whenLoaded` and `mergeWhen` methods, but the documentation is comprehensive.

#### The `whenLoaded` method <a href="#the-whenloaded-method" id="the-whenloaded-method"></a>

This method prevents data that has not been eagerly loaded from being loaded when retrieving related models thereby preventing the `(N+1)` query problem.

Still working with the Album resource as a point of reference (an album has many songs):

app/Http/Resources/AlbumResource.php

```
public function toArray($request)
{
    return [
        [...]
        // other attributes
        'songs' => SongResource::collection($this->whenLoaded($this->songs))
    ];
}
```

Copy

In the case where we are not eagerly loading songs when retrieving an album, we’ll end up with an empty songs collection.

#### The `mergeWhen` Method <a href="#the-mergewhen-method" id="the-mergewhen-method"></a>

Instead of having an if statement that dictates whether some attribute and its value will be part of the response, we can use the `mergeWhen()` method which takes in the condition to evaluate as the first argument and an array containing key-value pair that is meant to be part of the response if the condition evaluates to true:

app/Http/Resources/AlbumResource.php

```
public function toArray($request)
{
    return [
        [...]
        // other attributes
        'songs' => SongResource::collection($this->whenLoaded($this->songs)),
        $this->mergeWhen($this->songs->count > 10, ['new_attribute' => 'attribute value'])
    ];
}
```

Copy

This looks cleaner and more elegant instead of having if statements wrapping the entire return block.

### Step 5 — Unit Testing API Resources <a href="#step-5-unit-testing-api-resources" id="step-5-unit-testing-api-resources"></a>

Now that we’ve learned how to transform our responses, how do we verify that the response we get back is what we specified in our resource classes?

We’ll now write tests verifying the response contains the correct data as well as making sure eloquent relationships are still maintained.

Let’s create the test:

```
php artisan make:test SongResourceTest --unit
```

Copy

Notice the `--unit` flag when generating the test: this will tell Laravel that this will be a unit test.

**Note:** During verification, the error `Test already exists!` was observed when running the `make:test` command. The contents of `SongResourceTest.php` appear to include some older tests. Replace the contents of this file with the code provided for this tutorial.

Let’s start by writing the test to make sure our response from the `SongResource` class contains the correct data:

tests/Unit/SongResourceTest.php

```
[...]
use App\Http\Resources\SongResource;
use App\Http\Resources\AlbumResource;
[...]
class SongResourceTest extends TestCase
{
    use RefreshDatabase;
    public function testCorrectDataIsReturnedInResponse()
    {
        $resource = (new SongResource($song = factory('App\Song')->create()))->jsonSerialize();
    }
}
```

Copy

Here, we first create a song resource then call `jsonSerialize()` on the SongResource to transform the resource into JSON format, as that’s what will be sent to our frontend.

And since we already know the song attributes that will be part of the response, we can now make our assertion:

tests/Unit/SongResourceTest.php

```
[...]
$this->assertArraySubset([
    'title' => $song->title,
    'rating' => $song->rating
], $resource);
```

Copy

In this example, we’ve matched two attributes: `title` and `rating`. You can list multiple attributes.

If you want to make sure your model relationships are preserved even after converting models to resources, you can use:

tests/Unit/SongResourceTest.php

```
[...]
public function testSongHasAlbumRelationship()
{
    $resource = (new SongResource($song = factory('App\Song')->create(["album_id" => factory('App\Album')->create(['id' => 1])])))->jsonSerialize();
}
```

Copy

Here, we create a song with an `album_id` of `1` then pass the song on to the SongResource class before finally transforming the resource into JSON format.

To verify that the song-album relationship is still maintained, we make an assertion on the album attribute of the `$resource` we just created. Like so:

tests/Unit/SongResourceTest.php

```
[...]
$this->assertInstanceOf(AlbumResource::class, $resource["album"]);
```

Copy

Note, however, if we did `$this->assertInstanceOf(Album::class, $resource["album"])` our test would fail since we are transforming the album instance into a resource inside the SongResource class.

**Note:** During verification it was determined that we could run these tests with the following command:

```
vendor/bin/phpunit
```

Copy

As a recap, we first create a model instance, pass the instance to the resource class, convert the resource into JSON format before finally making the assertions.

### Conclusion <a href="#conclusion" id="conclusion"></a>

We’ve looked at what Laravel API resources are, how to create them as well as how to test out JSON responses. Feel free to explore the `JsonResource` class and see all the methods that are available.

If you would like to learn more about Laravel API resources, check the [official documentation](https://laravel.com/docs/5.6/eloquent-resources).
