Including relationships in Laravel API resource response

Viewed 13990

My aim is to create a book store API endpoint with a ratings relationship. The relationship between the Book & Rating is like this:

Book model

/**
* a book has many ratings
*/
public function ratings()
{
    return $this->hasMany(Rating::class, 'book_id')->orderBy('created_at', 'ASC');
}

Rating model

/**
* a rating belongs to a book
*/
public function book()
{
    return $this->belongsTo(Book::class);
}

My BookResource looks like this

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

use App\Http\Resources\RatingResource;

class BookResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        // return parent::toArray($request);
        return [
            'id'        => $this->id,
            'title'     => $this->title,
            'author'    => $this->author,
            'rating'    => RatingResource::collection($this->whenLoaded('ratings'))
        ];
    }
}

My RatingResource looks like this:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

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

I want to get all books along with the individual ratings and get a single book together with it's rating as well using both routes below in the api.php file:

Route::get('/books', function() {
    return BookResource::collection(Book::all());
});

Route::get('books/{book}', function(Book $book) {
    return new BookResource($book);
});

When I make a get request to http://12.0.0.1:8000/api/books on postman, I get all the books but the ratings are not loaded. The same thing happens when I make a get request to http://12.0.0.1:8000/api/books/1, the book is returned without the rating.

How are relationships loaded based on v5.7 doc

3 Answers

You should make: Book::with('ratings')->get(); for the /books endpoint and $book->load('ratings'); on /books/{book}.

Or, if you want to always load the relationship, define in the Book model: protected $with = ['ratings'];

For more info, checkout Laravel documentation: https://laravel.com/docs/5.7/eloquent-relationships#eager-loading

Try to use

class BookResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            ...
            'rating'    => RatingResource::collection($this->ratings)
        ];
    }
}

or if you want to use whenLoaded

return BookResource::collection(Book::with('ratings')->get());

As @honarkhah pointed out to me, I omitted loading the ratings when fetching books or a book so I fixed it by adding the load() when fetching the data like this

Route::get('/books', function() {
    return BookResource::collection(Book::all()->load('ratings'));
});

Route::get('books/{book}', function(Book $book) {
    return new BookResource($book->load('ratings'));
});
Related