In todays's tutorial, we are going to build a product reviews API using Laravel. In this API,Users will be able to add, view, update and delete products and also they will also be able to rate and review a product. We will also be implementing authentication with JSON Web Tokens (JWT) to secure our API.
First we need to create a new laravel project. To create new LAravel project run below command in your terminal:
1
laravel new product-review-api
After that you need to create your database and update the database credentials in the .env file.
Create models and migrations
For this project, we need three 3 models: user, product and review. By default laravel comes with a user model, so we have to create the remaining two. Let's start with the Product model:
1
php artisan make:model Product -a
The -a flag will create corresponding migration, controller, factory and seeder file for our model. Now we have to edit the product migration file as below because we need to define the columns for the products table and defining its relationship with the users table and what happens if the parent data is deleted.
Now we’ll need to create the Review model. To create Review model run below command in yout terminal:
1
php artisan make:model Review -a
Now we have to edit the Review migration file as below because we need to define the columns for the products table and defining its relationship with the users and product table and what happens if the parent data is deleted.
Now, we have already finished with our Migration. Now we need to run the migrate artisan command.
1
php artisan migrate
Model Relationships
While we are writing our migrations, you have seen hat we have defined our relationship between the users, products and reviews tables.
Let's see what relationship exists between this tables.
A user can add many products but a product can only belong to one user. This is a one to many relationship between user and product.
A product can have many reviews but a review can only belong to one product. This is a one to many relationship between product and review.
And finally user can make many reviews (be it same or different products) but a review can only belong to one user. This is a one to many relationship between the user and review.
Now we need to take this relationships to our Models.
Let us define the relationship between the user and the other models.
use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable;
class User extends Authenticatable { use Notifiable;
// Rest omitted for brevity
/** * Get the products the user has added. */ public function products() { return $this->hasMany('App\Product'); }
/** * Get the reviews the user has made. */ public function reviews() { return $this->hasMany('App\Review'); } }
Now in the product model we need to define the relationship between the product and the review model, and also the inverse relationship to the user model.
class Review extends Model { /** * Get the product that owns the review. */ public function product() { return $this->belongsTo('App\Product'); }
/** * Get the user that made the review. */ public function user() { return $this->belongsTo('App\User'); } }
Database Seeding
Database seeding means populating tables with data we want to develop. For this we use amazing library called Faker, without inputting this data manually. To do it paste below code in ProductFactory:
Next we need to write our seeders. Laravel comes with default user model, migration and factory files, but it doesn’t include a user seeder class. So we need to create User seeder by running the command:
1
php artisan make:seeder UserSeeder
This will create a UserSeeder.php file in the database\seeds folder. Open that file and edit like this:
1 2 3 4 5 6 7 8 9
/** * Run the database seeds. * * @return void */ public function run() { factory(App\User::class, 50)->create(); }
We are using the factory helper method to insert 50 user records into the users table.
To run our seeders, open the database\seeds\DatabaseSeeder.php file and uncomment the line
1
$this->call(UserSeeder::class);
Then we can use the db:seed artisan command to seed the database. Head over to your users and you should see 50 user records created.
1
php artisan db:seed
Let’s do the same for the ProductSeeder.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<?php
use Illuminate\Database\Seeder;
class ProductSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { factory(App\Product::class, 100)->create()->each(function ($product) { $product->reviews()->createMany(factory(App\Review::class, 5)->make()->toArray()); }); } }
It simply creating 100 products and for each product we are creating 5 reviews for that product.
To call additional seeders in our DatabaseSeeder.php file, we pass an array instead to the call method
Then we should need to use migrate:fresh command, which will drop all tables and re-run all of our migrations. The --seedflag instructs laravel to seed the database once the migration is completed.
1
php artisan migrate:fresh --seed
Now, we have already finished with our Database part. Le’s move on to Authentication part.
Authentication
To secure our application we are going to use the package jwt-auth for authentication with JSON Web Tokens (JWT). Run the below command in your terminal to install the package latest version:
1
composer require tymon/jwt-auth
Note: If you are using Laravel 5.4 and below, you need to manually register the service provider. Add the service provider to the providers array in the config/app.php config file as follows:
Next, you need to run the below command to generate a secret key which we use to sign our tokens.
1
php artisan jwt:secret
This will update your .env file with something like JWT_SECRET=value.
Now we need to update the user model to implement the Tymon\JWTAuth\Contracts\JWTSubject contract, which requires we implement the 2 methods getJWTIdentifier() and getJWTCustomClaims().
use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Tymon\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject { // Rest omitted for brevity
/** * Get the identifier that will be stored in the subject claim of the JWT. * * @return mixed */ public function getJWTIdentifier() { return $this->getKey(); }
/** * Return a key value array, containing any custom claims to be added to the JWT. * * @return array */ public function getJWTCustomClaims() { return []; } }
Now we need to make a few changes to the config/auth.php.To use laravel’s built in auth system with jwt-auth.
/** * Get the authenticated User. * * @return \Illuminate\Http\JsonResponse */ public function me() { return response()->json(auth()->user()); }
/** * Log the user out (Invalidate the token). * * @return \Illuminate\Http\JsonResponse */ public function logout() { auth()->logout(); return response()->json(['message' => 'Successfully logged out']); }
}
We use the auth middleware to only allow authenticated users to access to the application.
register method creates and stores a new user into the database.
login method allow user to log into the application.
respondWithToken() to return JWT response.
me to return the currently authenticated user.
logout to log out the currently authenticated user from the application.
Let's define our api routes. Open your routes/api.php and add the following routes.
Now we are going to work with our Product Endpoints.
GET/products - Fetch all products
GET/products/:id - Fetch a single product and its reviews
POST/products - Create a product
PUT/products/:id - Update a product
DELETE/products/:id - Delete a product
We can define this routes individually or use laravel resource routing feature in routes/api.php to define routes. In here we use the api resource route feature.
/** * Remove the specified resource from storage. * * @param \App\Product $product * @return \Illuminate\Http\Response */ public function destroy(Product $product) { if (auth()->user()->id !== $product->user_id) { return response()->json(['message' => 'Action Forbidden']); } $product->delete(); return response()->json(null, 204); } }
In the constructor, we use the auth middleware but excepting the index and show methods from using the middleware. It allows unauthenticated users to view all products and a single product.
index - returns a list of products ordered by the created date, the number of reviews the product has and paginates the records.
store - validates the request input and then creates a new product and attaches the product to the currently authenticated user and returns the newly created product.
show - returns a single product, the creator(user) and its reviews.
update - checks the currently authenticated user trying to update the record is the user that created the record. If the user is not the creator, a forbidden response is returned else we carry out the update operation.
delete - checks the currently authenticated user trying to delete the record is the user that created the record. If the user is not the creator, a forbidden response is returned else we carry out the delete operation.
Review Endpoints
Let's begin by defining the review endpoints:
POST/products/:id/reviews - Create a review for a product
PUT/products/:id/reviews/:id - Update a product review
DELETE/products/:id/reviews/:id - Delete a product review
We define the apiResource route for the reviews and specifying actions the controller should handle instead of the full set of default actions.
/** * Remove the specified resource from storage. * * @param \App\Product $product * @param \App\Review $review * @return \Illuminate\Http\Response */ public function destroy(Product $product, Review $review) { if (auth()->user()->id !== $review->user_id) { return response()->json(['message' => 'Action Forbidden']); } $review->delete(); return response()->json(null, 204); } }
Conclusion
In this tutorial, we obtain how can we create built a simple api and covered authentication, database seeding and CRUD operation. You can obtain complete source code for this tutorial from this GitHub repository.