Creating APIs In Laravel Using API Resources
This post is about how to use Laravel API resources feature to build a REST API. These API resources were presented in Laravel 5.5. Before this, a package like Fractal as a transformation layer was utilized to output JSON responses when structuring APIs.
So, if you are on this post you must be familiar with the basics Laravel & API resources. If you have Laravel installer ready on your computer already then let’s get started.
Brief Introduction To API Resources
API resources provides a way to transform our models in JSON responses easily. Between our Eloquent models & the JSON responses, API resources sits as an alteration layer. It’s primarily a combination of two units:
A resource class – a single model that needs to be transformed into JSON structure.
A resource collection – used for transforming collections of models into JSON structure.
Both of these resources class & the recourse collection can be created with Artisan Commands:
// create a resource class
$ php artisan make:resource UserResource
// create a resource collection using either of the two commands
$ php artisan make:resource Users –collections
$ php artisan make:resource UserCollection
What Are We Up To?
This is a step-to-step guide for building a book reviews API. User will be able to add, update & delete, review & view a list of all new books. The overall average rating will be computed based on the ratings on a particular book. Then final step is to add authentication with JSON Web Tokens (JWT) for a secure API.
Create A New Laravel App
We will start the work on the new Laravel app, utilizing Laravel installer:
$ laravel new book-reviews-api
Create A New Book Model
Before the resources that we will be using, we are to define the models they will be implementing. This book reviews API will be having three models: Book, User & Ratings. The User model doesn’t need to be created as it already comes with Laravel by default. So, the details will be on to create the remaining two & their corresponding migrations. Let’s start by creating the Book model:
$ php artisan make: model Book -m
The code of -m flag will create the corresponding migration file for the model.
Then repeat the same process for the rating model. As we already mentioned that user model comes off the shelf in laravel so we don’t need to work on that.
Next is the step to open the migration file generated for the Book model & edit or update the up functions as mentioned. On the directory edit
// database/migrations/TIMESTAMP_create_books_table.php
After it will look like this.
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 books table field and the ID of the author who added the book, the person who reviewed it & the ID of the book that was rated & the rating itself (from 0-5). The user ID is used as a foreign key that references the ‘id’ field in the ‘users’ table. Then some timestamps like created at and updated at are also added.
Next jump on the Rating migration,
// database/migrations/TIMESTAMP_create_ratings_table.php
Then tweak it to look like this.
<?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’);
}
}
Now run the command of $ php artisan migrate to run the migrations. Also, make sure to enter your database in the .env file before running the command above.
Define The Relationship Between The Models.
Unlimited books can be uploaded as long as it belongs to only one user. So, it is basically a one-to-many relationship. Let us help you by defining that.
Add the following code inside the User model.
// app/User.php
public function books ()
{
return $this->has Many (Book:class);
}
Next on the task list is to define the inverse relationship of the Book model.
// app/Book.php
public function user ()
{
return $this->belongs To (User:class);
}
Likewise, a book can be rated by various users, so one book can have many ratings. But a rating can only belong to one book. This is also termed as one-to-many relationship.
Further proceed & add the code in the Book model:
// app/Book.php
public function ratings ()
{
return $this->hasMany (Rating::class);
}
Then there is an inverse relationship inside the Ring model.
// app/Rating.php
public function book ()
{
return $this->belongs To (Book::class);
}
Creating The Book Resource.
Artisan commands have simplified making resource classes in Laravel. Run php artisan make: Resource BookResource that is created in app/Http/Resources/BookResource.php directory. Now navigate & edit the method toArray() in order to present it like this.
<?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’)
];
}
}
All resource class define this method which returns the array of attributes that should be converted to JSON when delivering the response. The Book Model needed is clear & defined that are converted to JSON. These attributes can also be removed as they don’t need to serialized to JSON. All the model properties can be directly access from the variable of $this.
Once the resource is defined it can be returned from a route or controller. Now BookResource class can be adjusted in our BookController, which we will be presenting to you all.
Creating the BookController.
Our BookController will use API controller generation defined by php artisan make: controller BookController –api. Open this controller that is located in app\Http\Controller\BookController.php and paste the following coding.
<?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 method of index () fetches & returns all the books that’s added within the program list. We will use the BookResource created earlier. The method of show()creates a new book with the ID of the authenticated user that already existed with the details of the new book.
Before the book is stored, the final validation check is very important. Now further
show() method returns the book resource on the newly created book.
update()confirms that the person who is trying to update the book is the one who has created it.
destroy() method deletes any specified book from the database.
Finally Define The API Routes.
In the last define the API routed & open routes\api.php to add the following code:
Route::apiResource(‘books’, ‘BookController’);
We are using apiResource()to generate API routes only. This will only generate specific routes of index, store, show, update & destroy.
So this is all for building an API with Laravel. Hopefully, all our readers are finding it interesting & helpful.