This is part two of a five part series, taking you through building a modern web app with Laravel and Vue. In part two, design your REST API.
In the last tutorial, we looked at how to successfully set up a development environment preparing us for PHP Development. Through the rest of the series, we will be building a simple “Trello Clone”.
In this tutorial, we will take a closer look at how setting up out application “RESTully” will play a role in us building modern apps using Vue and Laravel.
At the end of the series, what we’ll have will look like this:
This part of the series requires that you have:
To get started, we need to create our application. Since we have already set up our development environment in part one, all we need to is run the following command to create a new Laravel application:
1$ laravel new trello-clone
To give your application a test run, cd
into the trello-clone
directory and then run the command:
1$ php artisan serve
This runs your Laravel application on 127.0.0.1:8000
. To kill the server press ctrl+c
on your machine. If you have Laravel valet
installed and configured on your machine then you can cd
to the trello-clone
directory, run the command below:
1$ valet link trello-clone
Then head over to http://trello-clone.test
. You should see the Laravel welcome page as seen below:
For our simple trello clone, we are going to have the following models:
To build a model, run the following below. Since we already have the User
model, we need models for the Task
and Category
resources. You can go ahead and create the models now.
1$ php artisan make:model ModelName -mr
? The
-mr
flag creates an accompanying migration file and resource controller for the model.
Laravel comes bundled with a default User model so you do not need to create one. The User model will have the following fields:
id
– the unique auto-incrementing ID of the user.name
– the name of the user.email
– the email of the user.password
– used in authentication.Open the User model which is in the app
directory and update it as below:
1<?php 2 3 namespace App; 4 5 use Illuminate\Notifications\Notifiable; 6 use Illuminate\Foundation\Auth\User as Authenticatable; 7 use Illuminate\Database\Eloquent\SoftDeletes; 8 9 class User extends Authenticatable 10 { 11 use SoftDeletes, Notifiable; 12 13 protected $fillable = ['name', 'email', 'password']; 14 15 protected $hidden = [ 16 'password', 'remember_token', 17 ]; 18 19 public function tasks(){ 20 return $this->hasMany(Task::class); 21 } 22 }
? A fillable column in Laravel is a database column that is mass assignable. This means you can just pass an array to the
create
function of the model with the data you want to get assigned.?
SoftDeletes
is a way of deleting resources without actually deleting the data from the database. What happens is that when the table is created, there will be a field called ‘deleted_at ’ and when a user tries to delete a task, the ‘deleted_at’ field will be populated with the current date time. And so when fetches are made for resources, the ‘deleted’ resource will not be part of the response
The task model will have the following fields:
id
– a unique identifier for the task.name
– the name of the task.category_id
– ID of the category the task belongs to.user_id
– ID of the user the task belongs to.order
– the order of the task in its respective category.Create a Task model using the artisan command. Then open it from the app
directory and replace the contents with the following:
1<?php 2 3 namespace App; 4 5 use Illuminate\Database\Eloquent\Model; 6 use Illuminate\Database\Eloquent\SoftDeletes; 7 8 class Task extends Model 9 { 10 use SoftDeletes; 11 12 protected $fillable = ['name', 'category_id', 'user_id', 'order']; 13 14 public function category() { 15 return $this->hasOne(Category::class); 16 } 17 18 public function user() { 19 return $this->belongsTo(User::class); 20 } 21 }
The category model will have the following fields:
id
– this will uniquely identify every category.name
– represents the name of the category.Create a Category model using the artisan command. Then open it from the app
directory and replace the contents with the following:
1<?php 2 3 namespace App; 4 5 use Illuminate\Database\Eloquent\Model; 6 use Illuminate\Database\Eloquent\SoftDeletes; 7 8 class Category extends Model 9 { 10 use SoftDeletes; 11 12 protected $fillable = ['name']; 13 14 public function tasks() { 15 return $this->hasMany(Task::class); 16 } 17 }
Here, the tasks()
function is to help define relationships between the Category model and the Task model as a one-to-many
relationship. What this means is one category has many tasks.
For this application to work, we need to create the database. To keep track of changes going on in our database, we make use of migrations, which is an inbuilt feature of Laravel.
As part of the prerequisites mentioned in the first part of this series, you need SQLite installed on your machine. To create an SQLite database that Laravel can connect to create an empty new file in the database
directory called database.sqlite
.
Next, open your .env
file in the root of your project and replace the following lines:
1DB_CONNECTION=mysql 2 DB_DATABASE=homestead 3 DB_USERNAME=username 4 DB_PASSWORD=password
with
1DB_CONNECTION=sqlite 2 DB_DATABASE=/full/path/to/database/database.sqlite
That is all for our database setup. However, while you’re in the .env
file, change the APP_URL
value from http://localhost
to http://127.0.0.1:8000
as this will be the application URL.
If you wanted to create migrations, the artisan command is:
1$ php artisan make:migration create_tablename_table
You can name your migration file whatever you like, but it is always good to name it like verb_tablename_table
as shown above. The file will be created in the database/migrations
directory.
However since we have already used the -mr
flag earlier while creating our models, the migrations should have been created for us already.
⚠️ Migrations are runtime-based. So you need to consider this when making migrations for tables that are dependent on each other.
Open the create users migration file in the database/migrations
directory and replace the content with the code below:
1<?php 2 3 use Illuminate\Support\Facades\Schema; 4 use Illuminate\Database\Schema\Blueprint; 5 use Illuminate\Database\Migrations\Migration; 6 7 class CreateUsersTable extends Migration 8 { 9 public function up() 10 { 11 Schema::create('users', function (Blueprint $table) { 12 $table->increments('id'); 13 $table->string('name'); 14 $table->string('email')->unique(); 15 $table->string('password'); 16 $table->rememberToken(); 17 $table->timestamps(); 18 $table->softDeletes(); 19 }); 20 } 21 22 public function down() 23 { 24 Schema::dropIfExists('users'); 25 } 26 }
Since we had created the category migration earlier, open the file and replace the content with the code below:
1<?php 2 3 use Illuminate\Support\Facades\Schema; 4 use Illuminate\Database\Schema\Blueprint; 5 use Illuminate\Database\Migrations\Migration; 6 7 class CreateCategoriesTable extends Migration 8 { 9 public function up() 10 { 11 Schema::create('categories', function (Blueprint $table) { 12 $table->increments('id'); 13 $table->string('name'); 14 $table->timestamps(); 15 $table->softDeletes(); 16 }); 17 } 18 19 public function down() 20 { 21 Schema::dropIfExists('categories'); 22 } 23 }
Since we created the task migration file earlier, open the file and replace the content with the code below:
1<?php 2 3 use Illuminate\Support\Facades\Schema; 4 use Illuminate\Database\Schema\Blueprint; 5 use Illuminate\Database\Migrations\Migration; 6 7 class CreateTasksTable extends Migration 8 { 9 public function up() 10 { 11 Schema::create('tasks', function (Blueprint $table) { 12 $table->increments('id'); 13 $table->string('name'); 14 $table->unsignedInteger('category_id'); 15 $table->unsignedInteger('user_id'); 16 $table->integer('order'); 17 $table->timestamps(); 18 $table->softDeletes(); 19 20 $table->foreign('user_id')->references('id')->on('users'); 21 $table->foreign('category_id')->references('id')->on('categories'); 22 }); 23 } 24 25 public function down() 26 { 27 Schema::dropIfExists('tasks'); 28 } 29 }
Now that we have our migration files, let’s run the artisan command to execute the migrations and write to the database:
1$ php artisan migrate
? Migrations are like version control for your database. It allows you to create, modify or tear down your database as your application evolves, without having to manually write SQL queries (or whatever queries your database of choice uses). It also makes it easy for you and your team to easily modify and share the application’s database schema. Learn more.
Now that we have created our database migrations, let’s see how to put in dummy data for when we are testing our applications. In Laravel, we have something called seeders.
? Seeders allow you automatically insert dummy data into your database.
This is the command to make a seeder:
1$ php artisan make:seeder TableNameSeeder
To create our database seeder type the following command:
1$ php artisan make:seeder UsersTableSeeder
This creates a UsersTableSeeder.php
file in the database/seeds
directory. Open the file and replace the contents with the following code:
1<?php 2 3 use App\User; 4 use Illuminate\Database\Seeder; 5 6 class UsersTableSeeder extends Seeder 7 { 8 public function run() 9 { 10 User::create([ 11 'name' => 'John Doe', 12 'email' => 'demo@demo.com', 13 'password' => bcrypt('secret'), 14 ]); 15 } 16 }
The run
function contains the database query we want to be executed when the seeders are run.
? You can use model factories to create better-seeded data.
? We use the
bcrypt
to hash the password before storing it because this is the default hashing algorithm Laravel uses to hash passwords.
To create our database seeder type the following command:
1$ php artisan make:seeder CategoriesTableSeeder
This creates a CategoriesTableSeeder.php
file in the database/seeds
directory. Open the file and replace the contents with the following code:
1<?php 2 3 use App\Category; 4 use Illuminate\Database\Seeder; 5 6 class CategoriesTableSeeder extends Seeder 7 { 8 public function run() 9 { 10 $categories = ['Ideas', 'On Going', 'Completed']; 11 12 foreach ($categories as $category) { 13 Category::create(['name' => $category]); 14 } 15 } 16 }
To run the database seeders, open the database/DatabaseSeeder.php
file and replace the run
method with the code below:
1public function run() 2 { 3 $this->call([ 4 UsersTableSeeder::class, 5 CategoriesTableSeeder::class, 6 ]); 7 }
Next, run the command below on your terminal:
1$ php artisan db:seed
This should update the databases with data. If at some point you want to refresh and seed your data again, run the command below:
1$ php artisan migrate:fresh --seed
This will delete the database tables, add them back and run the seeder.
In technical terms, REST stands for REpresentational State Transfer (elsewhere, it just means “to relax”). In order for you get a good grasp of this article, there are a couple of terms that need to be broken down into bite-sized nuggets.
Clients are the devices that interact with your application. For any given application, the number of clients that interact with it can range from one to billions. When you go to a website (e.g. https://pusher.com) your client sends a request to the server. The server then processes your request and then sends a response back to your client for interaction.
Statelessness in the simplest of terms means building your application in such a way that the client has all it needs to complete every request. When the client makes subsequent requests, the server won’t store or retrieve any data relating to the client. When your application starts having more active concurrent users, it will be an unnecessary burden on your server managing states for the client. Being stateless also simplifies your application design so unless you have specific reasons not to be, then why not?
Resources are a representation of real-life instances in your code. Take for example you are building an application that allows students to check their grades, a good example of resources in such an application will be your students
, courses
etc. These resources are linked with the data that will be stored in the database.
Now when we are building RESTful applications, our server gives the client access to the resources. The client is then able to make requests to fetch, change, or delete resources. Resources are usually represented in JSON or XML formats but there are many more formats and it’s up to you during your implementation to decide the format.
Before we start creating the endpoints, make sure you get familiar with best practices for naming REST resources.
We currently have the following HTTP Verbs which we are going to apply:
GET
– this is usually used to fetch a resourcePOST
– this is used to create a new resourcePUT
/PATCH
– used to replace/update an existing resourceDELETE
– used to delete a resourceHere is a tabular representation of how our REST endpoints for our tasks resource will look:
METHOD | ROUTE | FUNCTION |
---|---|---|
POST | /api/task | Creates a new task |
GET | /api/task | Fetches all tasks |
GET | /api/task/{task_id} | Fetches a specific task |
PUT | PATCH | /api/task/{task_id} | Update a specific task |
DELETE | /api/task/{task_id} | Delete a specific task |
Let’s start creating the routes in our application. Open the routes/api.php
file and updated:
1<?php 2 3 Route::resource('/task', 'TaskController'); 4 Route::get('/category/{category}/tasks', 'CategoryController@tasks'); 5 Route::resource('/category', 'CategoryController');
Above, we defined our routes. We have two route resources that register all the other routes for us without having to create them manually. Read about resource controllers and routes here.
Earlier in the article, we spoke about making requests from the client. Now let’s look at how to create and format our responses when a request has been handled.
Now that we have our routes, we need to add some controller logic that will handle all our requests. To create a controller, you need to run the following command on the terminal:
1$ php artisan make:controller NameController
However since we have created our requests when we used the -mr
earlier, let’s edit them.
Open the controller file TaskController.php
in the app/Http/Controller/
directory. In there, we will define a few basic methods that’ll handle the routes we created above.
In the file update the store
method as seen below:
1public function store(Request $request) 2 { 3 $task = Task::create([ 4 'name' => $request->name, 5 'category_id' => $request->category_id, 6 'user_id' => $request->user_id, 7 'order' => $request->order 8 ]); 9 10 $data = [ 11 'data' => $task, 12 'status' => (bool) $task, 13 'message' => $task ? 'Task Created!' : 'Error Creating Task', 14 ]; 15 16 return response()->json($data); 17 }
? On line 16 we can see that the response is set to be in the JSON format. You can specify what response format you want the data to be returned in, but we will be using JSON.
⚠️ We are not focusing on creating the meat of the application just yet. We are explaining how you can create RESTful endpoints. In later parts, we will create the controllers fully.
Now that we have our routes, we have to secure them. As they are now, anyone can access them without having to verify that they should be able to.
Laravel, by default, has support for web and api routes. Web routes handle routing for dynamically generated pages accessed from a web browser, while, API routes handle requests from clients that require a response in either JSON
or XML
format.
In the first part of this series, we talked about API authentication using Laravel Passport. If you read this guide, you would already have the idea of how to make this work. For that reason, we would go over a lot of things fairly quickly in this section.
First, install Laravel Passport:
1$ composer require laravel/passport
Laravel Passport comes with the migration files for the database table it needs to work, so you just need to run them:
1$ php artisan migrate
Next, you should run the passport installation command so it can create the necessary keys for securing your application:
1php artisan passport:install
The command will create encryption keys needed to generate secure access tokens plus “personal access” and “password grant” clients which will be used to generate access tokens.
After the installation, you need to use
the Laravel Passport HasApiToken
trait in the User
model. This trait will provide a few helper methods to your model which allow you to inspect the authenticated user’s token and scopes.
File: app/User.php
1<?php 2 3 [...] 4 5 use Laravel\Passport\HasApiTokens; 6 7 class User extends Authenticatable 8 { 9 use HasApiTokens, SoftDeletes, Notifiable; 10 11 [...] 12 }
Next, call the Passport::routes
method within the boot
method of your AuthServiceProvider
. This method will register the routes necessary to issue the tokens your app will need:
File: app/Providers/AuthServiceProvider.php
1<?php 2 3 [...] 4 5 use Laravel\Passport\Passport; 6 7 class AuthServiceProvider extends ServiceProvider 8 { 9 [...] 10 11 public function boot() 12 { 13 $this->registerPolicies(); 14 15 Passport::routes(); 16 } 17 18 [...] 19 }
Finally, in your config/auth.php
configuration file, you should set the driver
option of the api
authentication guard to passport
.
File: config/auth.php
1[...] 2 3 'guards' => [ 4 [...] 5 6 'api' => [ 7 'driver' => 'passport', 8 'provider' => 'users', 9 ], 10 ], 11 12 [...]
Now that you have set up the API authentication for this application using Laravel Passport, we will need to make the login and registration endpoints.
Add the following routes in routes/api.php file:
1Route::post('login', 'UserController@login'); 2 Route::post('register', 'UserController@register');
You also need to create the UserController
to handle authentication requests for the API. Create a new file UserController.php
in app/Http/Controllers
and place the following code in it:
1<?php 2 3 namespace App\Http\Controllers; 4 5 use App\User; 6 use Validator; 7 use Illuminate\Http\Request; 8 use App\Http\Controllers\Controller; 9 use Illuminate\Support\Facades\Auth; 10 11 class UserController extends Controller 12 { 13 public function login() 14 { 15 $credentials = [ 16 'email' => request('email'), 17 'password' => request('password') 18 ]; 19 20 if (Auth::attempt($credentials)) { 21 $success['token'] = Auth::user()->createToken('MyApp')->accessToken; 22 23 return response()->json(['success' => $success]); 24 } 25 26 return response()->json(['error' => 'Unauthorised'], 401); 27 } 28 29 public function register(Request $request) 30 { 31 $validator = Validator::make($request->all(), [ 32 'name' => 'required', 33 'email' => 'required|email', 34 'password' => 'required', 35 ]); 36 37 if ($validator->fails()) { 38 return response()->json(['error' => $validator->errors()], 401); 39 } 40 41 $input = $request->all(); 42 $input['password'] = bcrypt($input['password']); 43 44 $user = User::create($input); 45 $success['token'] = $user->createToken('MyApp')->accessToken; 46 $success['name'] = $user->name; 47 48 return response()->json(['success' => $success]); 49 } 50 51 public function getDetails() 52 { 53 return response()->json(['success' => Auth::user()]); 54 } 55 }
In the code above we have the:
Login Method: in here we call Auth::attempt
with the credentials the user supplied. If authentication is successful, we create access tokens and return them to the user. This access token is what the user would always send along with all API calls to have access to the APIs.
Register Method: like the login method, we validated the user information, created an account for the user and generated an access token for the user.
For our routes, we can group the routes we need authentication for under common middleware. Laravel comes with the auth:api
middleware in-built and we can just use that to secure some routes as seen below in the routes/api.php
file:
1<?php 2 3 Route::post('login', 'UserController@login'); 4 Route::post('register', 'UserController@register'); 5 6 Route::group(['middleware' => 'auth:api'], function(){ 7 Route::resource('/task', 'TasksController'); 8 Route::resource('/category', 'CategoryController'); 9 Route::get('/category/{category}/tasks', 'CategoryController@tasks'); 10 });
In the event that our server encountered errors while serving or manipulating our resources, we have to implement a way to communicate to the client that something went wrong. For this, we have to serve the responses with specific HTTP status codes.
If you look at the UserControlle``r.php
file you can see us implementing HTTP status code 401 which signifies that the client is not authorized to view the resource:
1public function login(Request $request) 2 { 3 $status = 401; 4 $response = ['error' => 'Unauthorised']; 5 6 [...] 7 8 return response()->json($response, $status); 9 }
In this part of the series, we have considered how you can create RESTful endpoints for your application. We also considered how you can handle errors and serve the correct HTTP status code to the client.
In the next chapter, we will address how to test your API endpoints using Postman. We will set up some unit tests, which will be useful for our own testing from the command-line.