Laravel 8 - API Resources for Beginners (10 Steps) ⭐⭐⭐
Tags: php, laravel, api, beginners
Published: 2022-02-22 03:07:37

What does API Resources mean?

**API Resources** acts as a transformation layer that sits between our Eloquent models and the JSON responses that are actually returned by our API.
**API resources** present a way to easily transform our models into `JSON responses`. 

Sample commands for **creating the resource class** and resource collection using php artisan 

**create a resource class**
```
$ php artisan make:resource UserResource
```

**create a resource collection using either**
```
$ php artisan make:resource Users --collection
$ php artisan make:resource UserCollection
```

This is the representation of the database api we are gonna create.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dpk5c3l243xo100ob326.png)
 

## Lets start by creating a new laravel app
`$ laravel new book-reviws-api`

## STEP 1: Create models and migrations

The book reviews API will have three models: User, Book and Rating.

We’ll start by creating the `Book` model:
` $ php artisan make:model Book -m`

Next, let’s open the migration file generated for the Book model and update the up() method as below:

**database/migrations/TIMESTAMP_create_books_table.php**
```
 public function up()
    {
      Schema::create('books', function (Blueprint $table) {
        $table->increments('id');
        $table->unsignedInteger('user_id');
        $table->string('title');
        $table->text('description');
        $table->timestamps();
      });
    }
```

We’ll do the same for the `Rating` model:
`$ php artisan make:model Rating -m`

Open the migration file generated for the Rating model and update the up() method as below:

**database/migrations/TIMESTAMP_create_ratings_table.php**

```
public function up()
    {
      Schema::create('ratings', function (Blueprint $table) {
        $table->increments('id');
        $table->unsignedInteger('user_id');
        $table->unsignedInteger('book_id');
        $table->unsignedInteger('rating');
        $table->timestamps();
      });
    }
```

Run the the command below to run the migrations:
`$ php artisan migrate`

Remember to enter your database details in the `.env` file before running the command above.

## STEP 2: Define relationships between models

> A user can add as many books as they wish, but a book can only belong to one user. So, the relationship between the User model and Book model is a `one-to-many` relationship. 

Let’s define that. Add the code below inside the User model:

**app/User.php**
```
public function books()
    {
      return $this->hasMany(Book::class);
    }
```
Next, let’s define the inverse relationship on the Book model:

**app/Book.php**
```
public function user()
    {
      return $this->belongsTo(User::class);
    }
```

> Likewise, a book can be rated by various users, hence a book can have many ratings. A rating can only belong to one book. This is also a `one-to-many` relationship. 

Add the code below in the Book model:

**app/Book.php**
```
public function ratings()
    {
      return $this->hasMany(Rating::class);
    }
```

Then we define the inverse relationship inside the Rating model:

**app/Rating.php**
```
public function book()
    {
      return $this->belongsTo(Book::class);
    }
```

## STEP 3: Allowing mass assignment on some fields

> To avoid getting the mass assignment error which Laravel will throw by default, we need to specify the columns we want to be mass assigned.
 

Let’s add the snippet below to our models respectively:

**app/Book.php**
```
protected $fillable = ['user_id', 'title', 'description'];
```

**app/Rating.php**
```
protected $fillable = ['book_id', 'user_id', 'rating'];
```

## STEP 4: Defining API routes
Let’s define our routes. Open `routes/api.php` and add the line below to it:

```
  Route::apiResource('books', 'BookController');
    Route::post('books/{book}/ratings', 'RatingController@store');
```

Since we are building an API, we make use of `apiResource()` to generate API only routes.

 Also, we define a route that will be used to rate a specified book. For instance, `/books/53/ratings` will be used to rate the book with the ID of `53`.

> Note:
> When building APIs with Laravel, it is recommended to use the `apiResource()` method while defining resourceful routes, this will generate only API specific routes (`index`, `store`, `show`, `update` and `destroy`). Unlike when you use the `resource()` method, which will in addition to generating API specific routes, also generate `create` and `edit` routes, which aren’t needed when building an API.

## STEP 5: Creating the book resource
Before we move on to create the` BooksController`, let’s create a book resource class. We’ll make use of the artisan command `make:resource` to generate a new book resource class. By default, resources will be placed in the `app/Http/Resources` directory of our application.

`$ php artisan make:resource BookResource`

Once that is created, let’s open it and update the `toArray()` method as below:

**app/Http/Resources/BookResource.php**
```
 public function toArray($request)
    {
      return [
        'id' => $this->id,
        'title' => $this->title,
        'description' => $this->description,
        'created_at' => (string) $this->created_at,
        'updated_at' => (string) $this->updated_at,
        'user' => $this->user,
        'ratings' => $this->ratings,
      ];
    }
```

As the name suggests, this will transform the resource into an array.

We can access the model properties directly from the `$this` variable.

Now we can make use of the `BookResource` class in our controller.

## STEP 6: Creating the book controller
Let’s create the `BookController`. For this, we’ll make use of the API controller generation feature 
`$ php artisan make:controller BookController --api`


Next, open it up and paste the following code into it:

**app/Http/Controllers/BookController.php**
```   
    use App\Book;
    use App\Http\Resources\BookResource;

// ...

    public function index()
    {
      return BookResource::collection(Book::with('ratings')->paginate(25));
    }

    public function store(Request $request)
    {
      $book = Book::create([
        'user_id' => $request->user()->id,
        'title' => $request->title,
        'description' => $request->description,
      ]);

      return new BookResource($book);
    }

    public function show(Book $book)
    {
      return new BookResource($book);
    }

    public function update(Request $request, Book $book)
    {
      // check if currently authenticated user is the owner of the book
      if ($request->user()->id !== $book->user_id) {
        return response()->json(['error' => 'You can only edit your own books.'], 403);
      }

      $book->update($request->only(['title', 'description']));

      return new BookResource($book);
    }

    public function destroy(Book $book)
    {
      $book->delete();

      return response()->json(null, 204);
    }
```

## Functionalities:

The `index()` method fetches and returns a list of the books that have been added. 

The `store()` method creates a new book with the ID of the currently authenticated user along with the details of the book, and persists it to the database. Then we return a book resource based on the newly created book.

The `show()` method accepts a Book model (we are using route model binding here) and simply returns a book resource based on the specified book.

The `update()` method first checks to make sure the user trying to update a book is the owner of the book . If the user is not the owner of the book, we return an appropriate error message and set the HTTP status code to `403`. Otherwise we update the book with the new details and return a book resource with the updated details.

Lastly, the `destroy()` method deletes a specified book from the database. Since the specified book has been deleted and no longer available, we set the HTTP status code of the response returned to 204.

## STEP 7: Creating the rating resource

Just as we did with the `BookResource`, we’ll also create a rating resource class:
`$ php artisan make:resource RatingResource`

Once that is created, let’s open it and update the `toArray()` method as below:

**app/Http/Resources/RatingResource.php**
```
public function toArray($request)
    {
      return [
        'user_id' => $this->user_id,
        'book_id' => $this->book_id,
        'rating' => $this->rating,
        'created_at' => (string) $this->created_at,
        'updated_at' => (string) $this->updated_at,
        'book' => $this->book,
      ];
    }
```

Again, we pass along the attributes we want to be converted to JSON when sending the response. 

## STEP 8: Creating the rating controller

Next, create the `RatingController` that will make use of the `RatingResource`:
`$ php artisan make:controller RatingController`

Next, open it up and paste the following code into it:

**app/Http/Controllers/RatingController.php**
```
 use App\Book;
    use App\Rating;
    use App\Http\Resources\RatingResource;

// ...

    public function store(Request $request, Book $book)
    {
      $rating = Rating::firstOrCreate(
        [
          'user_id' => $request->user()->id,
          'book_id' => $book->id,
        ],
        ['rating' => $request->rating]
      );

      return new RatingResource($rating);
    }
```

The `store()` is used to rate a specified book. We are using the `firstOrCreate()` which checks if a user has already rated a specified book.

We add the user rating to the specified book and persist it to the database. Then we return a rating resource based on the newly added rating.

## STEP 9: Getting average rating

The last feature that’s left is getting the average rating made on a book

Add the line of code below to the `toArray()` method of 

**app/Http/Resources/BookResource.php**
```
'average_rating' => $this->ratings->avg('rating')
```

We are using the `ratings` relationship defined of the Book model to fetch all the ratings that have be made on the specified book. 

Then, using collection `avg()`, we get the average of the ratings.

 Passing `rating` to the `avg()` function indicates that we want to calculate the average based on the book rating.

Now, whenever the `BookResource` is used, the response will contain the average rating of the book.

Sample book resource response:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hjyf3flcex1yzdvdhg16.PNG)

Sample rating resource response:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ljzplzx7nzhkae4m8jbn.PNG)


## STEP 10: Handling resource not found

By default when a specified model is not found, Laravel will throw a `ModelNotFoundException` and renders a `404` page. Since we are building an API, we want to handle the exception and throw an API friendly error message.

Add the code below to the `render` method of 

**app/Exceptions/Handler.php**
```
if ($exception instanceof ModelNotFoundException && $request->wantsJson()) {
      return response()->json([
        'error' => 'Resource not found'
      ], 404);
    }
```

This checks if the exception thrown is an instance of `ModelNotFoundException` and the request wants JSON, then we simply return a response with an error message of **Resource not found** and set the HTTP status code to `404`


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tr6pij65kmm3u8k5m0jf.gif)

> Don't forget! The API requests will need the header Accept: application/json in postman.