How to create an API with laravel resources
Tags: laravel, php, beginners, api
Published: 2019-11-09 14:33:48

In [part 1](https://dev.to/ted/handling-user-registration-and-authentication-on-a-laravel-api-using-jwt-4751) of this series we built a registration/login API for our app.
In this part of the series, we will continue with the laravel app by building a book review API that allows registered users to upload books, edit them, delete them as well as review their books and other books.
we shall be using laravel API resources.
###what are API resources?
laravel API resources act as a transformation layer that sits between your Eloquent models and the JSON responses that are returned to your application's users. Laravel's resource classes allow you to expressively and easily transform your models and model collections into JSON.
For more insight into how they work you can check out the official [laravel documentation](https://laravel.com/docs/5.7/eloquent-resources).
Before we make the resources we'll be using, we have to define the models they will be implementing. For this guide, we will have Book, Rating and user models.
To make the Book model, run this command ```php artisan make: model Book -m```
the -m flag makes a corresponding migration file for the model. Repeat the process for the rating model. We don't need to make a user model since it comes off the shelf in laravel. 
Next, we will edit the 'up' functions in the migrations we've just created. On the directory ``// database/migrations/TIMESTAMP_create_books_table.php`` edit it to look like this
``` php
class CreateBooksTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('books', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->Integer('user_id')->unsigned();
            $table->string('title');
            $table->string('author');
            $table->text('description');
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
        });

    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('books');
    }
}
```
we define the field of the books table as well as the ID of the user who added the book. The user ID is a foreign key that references the 'id' field in the 'users' table.
Next, open the Rating migration, ``// database/migrations/TIMESTAMP_create_ratings_table.php`` and tweak it to look like this,
``` php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateRatingsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('ratings', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedInteger('user_id');
            $table->unsignedInteger('book_id');
            $table->unsignedInteger('rating');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('ratings');
    }
}
```
In this migration, we defined the fields that the ratings table will take, which are the ID,which is the primary key of the ratings table, the user_id of the user who posted a specific rating, the book_id to which the rating was posted, the rating itself(out 0f 5) and the timestamps the rating was created or updated.
After running all the above, let's run our migrations, but before we do, navigate to the ``app\providers\AppServiceProvider.php`` file and edit the boot function by adding the defaultStringthLength so that it looks like this 
``` php 
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Schema;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Schema::defaultStringLength(191);
    }
}
```
This will handle any migration errors usually caused by MariaDB(such as the one XAMPP has) or older versions of MySQL. Now that we've set it up, run ``php artisan migrate`` 
###Define Relationships between models
For the application logic of this API, let's understand how the models are related and the types of relationships we have to define for it to fit well together.
1. A user can add many books but a book can only belong to one user -> this will be a ``one-to-many relationship`` between the User model and the Book model.
2. A book can be rated by many users hence many ratings but a rating can only belong to one book -> this will be also a ``one-to-many`` relationship between the Book model and the rating model.
Okay, now that I have explained that, let's define these relationships in our models. Open the ``app\User.php`` and add this function
``` php
public function books()
    {
        return $this->hasMany(Book::class);
    }
```
Likewise, edit ``app\Book.php`` by adding
``` php
public function User()
    {
        return $this->belongsTo(User::class);
    }
    public function ratings()
    {
        return $this->hasMany(Rating::class);
    }
```
Here, we have defined the relationship between the book model and the user model as well as the rating model. Next we will define the relationship between the rating model and the Book model by editing ``app/Rating.php`` file to look like this
```php 
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Rating extends Model
{
    public function book()
    {
        return $this->belongsTo(Book::class);
    }
    protected $fillable = ['book_id','user_id','rating'];
}
```
Apart from defining the relationship, we have allowed mass assignment of the fields that the rating model will accept, which are the book_id,user_id and the rating which the users have to fill.
For the Book model, we will allow mass assignment by adding
``` php
 protected $fillable = ['user_id','title','author','description'];
```
###Create the Book Resource
Making resource classes in laravel is simplified by using artisan commands. To make the Book resource class, run ``php artisan make:Resource BookResource``
which is created in ``app/Http/Resources/BookResource.php`` directory. Navigate here and edit the ``toArray()`` method so that it looks like this:
``` php 
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class BookResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        //transforms the resource into an array made up of the attributes to be converted to JSON
        return [
            'id'=> $this->id,
            'title'=> $this->title,
            'author' => $this->author,
            'description'=>$this->description,
            'created_at' => (string) $this->created_at,
            'updated_at' => (string) $this->updated_at,
            'user' => $this->user,
            'ratings' => $this->ratings,
            'average_rating' => $this->ratings->avg('rating')
        ];

    }
}
```
Every resource class defines a toArray method which returns the array of attributes that should be converted to JSON when sending the response. All the attributes of the Book Model we need converted to JSON are defined, we can also remove those attributes we do not need to be serialized to JSON. Notice that we can access model properties directly from the ``$this`` variable. This(pun intended) is because a resource class will automatically proxy property and method access down to the underlying model for convenient access. Once the resource is defined, it may be returned from a route or controller. Now we can make use of the BookResource class in our ``BookController`` which we will now make.
###Creating the Book Controller
Our BookController will make use of the API controller generation feature, which is defined by ``php artisan make:controller BookController --api``
open this controller, located in ``app\Http\Controller\BookController.php`` and paste this 
``` php
<?php

namespace App\Http\Controllers;

use App\Book;
use Illuminate\Http\Request;
use App\Http\Resources\BookResource;

class BookController extends Controller
{
   public function __construct()
   {
    $this->middleware('auth:api')->except(['index', 'show']);
   }
   
    /**
     * Display all books that have been added
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
      return BookResource::collection(Book::with('ratings')->paginate(25));
    }
  /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $this->validate($request, [
            'title' => 'required',
            'description' => 'required',
            'author' => 'required',
        ]);
        $book = new Book;
        $book->user_id = $request->user()->id;
        $book->title = $request->title;
        $book->description = $request->description;
        $book->author = $request->author;
        $book->save();

        return new BookResource($book);
}
    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show(book $book)
    {
        return new BookResource($book);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    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','author', 'description']));



      return new BookResource($book);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy(Request $request,Book $book)
    {
      if($request->user()->id != $book->user_id){
        return response()->json(['error' => 'You can only delete your own books.'], 403);
      }
        $book ->delete();
        return response()->json(null,204);
    }
}
```
The ``index()`` method fetches and returns a list of all the books that have been added. We are making use of the ``BookResource`` created earlier.
The ``store()`` method creates a new book with the ID of the currently authenticated user along with the details of the book. Before the book is stored it has to be validated that all the fields have been entered, hence the validation check. A BookResource is returned based on the newly created book.
The ``show()`` method returns a book resource based on the specified book.
The ``update()`` method first checks to make sure that the user trying to update a book is the user who created it. If not, we return a message that one can only edit their book. If the person is the owner, they can update the book with the new details and return a book resource with the updated details.
The ``destroy()`` method deletes a specified book from the database. However, one can only deleted a book they uploaded. Therefore we set the HTTP status code 403 with an error message notifying one that they can only delete their own books if they try to delete other people's books. If the user is the owner of the book, we set the HTTP status code of the response returned to 204, which indicates that there is no content to return since the specified resource has been deleted.
Also, note that we have secured our API endpoints by using the ``auth::api`` middleware on the ``__construct`` function. We are exempting the index and show methods from implementing the middleware. This way, unauthenticated users will be able to see a list of all books and a particular book.
##Defining API routes
Finally, we will define the API routes. open ``routes\api.php`` and add the following
``` php
Route::apiResource('books', 'BookController');
```
We are making use of ``apiResource()`` to generate API only routes. It is recommended to use this method as it will generate only API specific routes(index, store, show, update and destroy). Unlike using the normal ``resource()`` method which, in addition to generating the API specific routes, also generates ``create()`` and ``edit()`` routes which are aren't necessary when building an API.

That's it for part 2 of building an API with laravel, I had hoped to finish with 3 parts but due to the volume of content, I'll have to add more parts, which will cover the rating functionality as well as a final part that will cover testing of the app to make sure that it actually does work. So don't go anywhere :-)
I'd like to mention that the inspiration for this came from [this post](https://blog.pusher.com/build-rest-api-laravel-api-resources/) I found on [pusher.com](https://pusher.com/) which is a good starting point. I found some errors, I'm trying to fix by writing this series.
You can also find the source code on my [github](https://github.com/King-maker/book-review-api)


## Sponsors

>  Please note that some of the links below are affiliate links. I only recommend products, tools and learning services I've personally used and believe are genuinely helpful. Most of all, I would never advocate for buying something you can't afford or that you aren't ready to use.

- [Scraper API](https://www.scraperapi.com?via=teddy44) is a startup specializing in strategies that'll ease the worry of your IP address from being blocked while web scraping. They utilize IP rotation so you can avoid detection. Boasting over 20 million IP addresses and unlimited bandwidth. Using Scraper API and a tool like [2captcha](https://2captcha.com?from=12437369) will give you an edge over other developers. The two can be used together to automate processes. Sign up on Scraper API and use [this link] (https://www.scraperapi.com?via=teddy44) to get a 10% discount on your first purchase.

- Do you need a place to host your website or app, [Digital ocean](DigitalOcean – The developer cloudwww.digitalocean.com)
 is just the solution you need, sign up on digital ocean using this [link](https://m.do.co/c/eaa803fe4d99) and experience the best cloud service provider.

- The journey to becoming a developer can be long and tormentous, luckily [Pluralsight](http://referral.pluralsight.com/mQh0Nxp) makes it easier to learn. They offer a wide range of courses, with top quality trainers, whom I can personally vouch for. Sign up using [this link](http://referral.pluralsight.com/mQh0Nxp) and get a 50% discount on your first course.