PHP - - By Francesco Malatesta

How to Build an API-Only JWT-Powered Laravel App

JSON Web Tokens and Laravel API apps

The Laravel API Boilerplate (JWT Edition) was made on the shoulders of giants like:

In this article, we will learn how to use it to quickly create a fully functional API for an imaginary book wishlist application. As an aside, we will also see how to build a client application with AngularJS that will use our APIs.

Laravel Logo

Creating the Project

This tutorial assumes a working PHP environment has been set up, something like Homestead Improved. Let’s install our API Boilerplate. All we have to do is to clone the Github repository, with

git clone https://github.com/francescomalatesta/laravel-api-boilerplate-jwt Laravel

then run

composer install

to install dependencies and make all the basic configuration operations. Both Laravel and JWT keys will be automatically generated.

Everything went well

Nothing more to do! Let’s build the server part of our app!

Creating the API

Before doing anything, we have to plan our application’s details and features. We are going to build a basic book wishlist, so we will be using two main entities: User and Book.

A User will be able to:

  • sign up, in order to create a new account;
  • log in, if they already have an account;
  • log out, to leave the application;
  • get a list of their books;
  • add a new book to the wishlist;
  • edit an existing book;
  • delete an existing book from the list;

Note that for every User we will ask for:

  • name;
  • email address;
  • password;

A Book will have a:

  • title;
  • author name;
  • pages count;

Finally, every list will show books starting from the most recent one.

First step: the User part.

Building the User Part

The good news is that the User part is already completed.

If we open the app/Api/V1/Controllers/AuthController.php file, we can see that it already has a signup and a login method! They are not the the ones we can see in the standard AuthController in a standard new Laravel project: here they were re-implemented to have a token-based sign up and log in procedure.

Now, let’s take a look at the signup method, in particular.

public function signup(Request $request)
{
    $signupFields = Config::get('boilerplate.signup_fields');
    $hasToReleaseToken = Config::get('boilerplate.signup_token_release');

    $userData = $request->only($signupFields);

    $validator = Validator::make($userData, Config::get('boilerplate.signup_fields_rules'));

    if($validator->fails()) {
        throw new ValidationHttpException($validator->errors()->all());
    }

    User::unguard();
    $user = User::create($userData);
    User::reguard();

    if(!$user->id) {
        return $this->response->error('could_not_create_user', 500);
    }

    if($hasToReleaseToken) {
        return $this->login($request);
    }
    
    return $this->response->created();
}

As we can see from the code, we are using some options in a config file. Specifically, we are getting two values from this file:

  • boilerplate.signup_fields: the fields list we want to use for the signup operation;
  • boilerplate.signup_fields_rules: the validation rules we want our data checked against during the signup operation;

We can find all the boilerplate options in config/boilerplate.php.

There’s also another option used here: boilerplate.signup_token_release. When set to true, a token is automatically released after signup. This means that the user can start using your app immediately.

Note: for this project we are going to use 24-hour tokens. The default value for the token’s life-span is 60 minutes. This can be changed in config/jwt.php at the ttl value.

Back to our API. The actual setting for boilerplate.signup_fields is

'signup_fields' => [
    'name', 'email', 'password'
],

… and it’s fine, considering our needs. Some basic rules are there, too:

'signup_fields_rules' => [
	'name' => 'required',
	'email' => 'required|email|unique:users',
	'password' => 'required|min:6'
],

So far, we haven’t had to do anything. Also, we already have our routes in the app/Http/api_routes.php file.

// app/Http/api_routes.php

$api = app('Dingo\Api\Routing\Router');

$api->version('v1', function ($api) {

	$api->post('auth/login', 'App\Api\V1\Controllers\AuthController@login');
	$api->post('auth/signup', 'App\Api\V1\Controllers\AuthController@signup');

	...

});

The router we’re using here is a little bit different from the normal one we see in Laravel. We are, in fact, using the custom Dingo API Router.

The user part is now complete without typing a single line of code.

The only one remaining part is the logout procedure. However, a RESTful API is stateless by definition and this is something we are going to implement in the client.

Now, we should at least test our two procedures, to be sure. The first thing to do is to migrate our database. In our terminal, let’s type

	php artisan migrate

Let’s test a basic signup procedure. We’ll open an HTTP Client and make a call to the signup route. For this article’s purposes, we will use Postman. Let’s fill our request body…

Request body filled out

… send it and wait for the result …

Result produced

Done! Our user is now added to the application. To be sure, let’s make another test. This time for the login.

Logging in via postman

Great! The login procedure also works fine! We don’t need anything else for the User part. Time to finish our API by implementing the Book part.

Building the Book Part

In order to implement the Book side, we will need a migration to define our schema structure, a model and, finally, a controller to handle all the operations. Let’s start with migrations. Let’s open the terminal and type

	php artisan make:migration create_books_table --create=books

and a new file will be created in database/migrations. We then add the following fields in the up() method.

public function up()
{
    Schema::create('books', function (Blueprint $table) {
        $table->increments('id');

		// adding specific fields here...
        $table->string('title');
        $table->string('author_name');
        $table->integer('pages_count');

        $table->integer('user_id')->index();

        $table->timestamps();
    });
}

Time to “migrate” again, by typing

	php artisan migrate

to add the new table to the schema. Now, let’s create the model, with

	php artisan make:model Book

and we are done. Due to the conventions in Laravel, the Book model already “knows” that it has to work with the books table we have created. Here’s the model:

namespace App;

use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
    protected $fillable = ['title', 'author_name', 'pages_count'];
}

We are going to put these fields (title, author_name and pages_count) into the fillable array in order to use the fill method on the update procedure (more on that soon).

Now, we have to edit our User model in order to define the relationship we will need to retrieve their related books. In app\User.php we add the following method:

public function books()
{
    return $this->hasMany('App\Book');
}

The last step is the resource controller for the Book entity. Let’s type

	php artisan make:controller BookController

to create a new resource controller. Laravel will create it in the app/Http/Controllers folder. We will move it to app/Api/V1/Controllers.

We also change the namespace:

namespace App\Api\V1\Controllers;

where App is the basic namespace we have chosen for our application. By default, we can leave it as it is.

Before proceeding, we have to remember to add some use directives at the beginning of the file:

use JWTAuth;
use App\Book;
use Dingo\Api\Routing\Helpers;

The first one is the JWTAuth Facade, which we will use to retrieve our user data from the token. The second one is our model. The third is a helper we will use to quickly create every response for outputting to the client in a RESTful manner.

We must also remember to include this Helpers trait in the controller:

...

class BookController extends Controller
{
    use Helpers;

...

Now we can implement our methods:

  • index, to get the books list of a certain user;
  • show, to get a single book given its id;
  • store, to store a newly created book in the database;
  • update, to store updates on a certain book, given its id;
  • destroy, to delete an existing book from the database, given its id;

Let’s start from the first: index().

public function index()
{
    $currentUser = JWTAuth::parseToken()->authenticate();
    return $currentUser
        ->books()
        ->orderBy('created_at', 'DESC')
        ->get()
        ->toArray();
}

This is really easy to understand. We are getting our user starting from the token, and then we are returning all the related books as output. Nothing more.

We can make a step forward, to store()

public function store(Request $request)
{
    $currentUser = JWTAuth::parseToken()->authenticate();

    $book = new Book;

    $book->title = $request->get('title');
    $book->author_name = $request->get('author_name');
    $book->pages_count = $request->get('pages_count');

    if($currentUser->books()->save($book))
        return $this->response->created();
    else
        return $this->response->error('could_not_create_book', 500);
}

Like before, we are getting the current user with the JWTAuth method, parseToken()->authenticate(). Then, we are creating our instance and saving the relationship with the save method.

If everything goes right, a Created 201 response will be returned. Otherwise, the client will get a custom 500 error. As we can see, it’s not just about the body: headers also will be tweaked accordingly to follow RESTful standards.

Let’s do some tests before finishing our controller. Add the following code into api_routes.php:

$api->group(['middleware' => 'api.auth'], function ($api) {
	$api->post('book/store', 'App\Api\V1\Controllers\BookController@store');
	$api->get('book', 'App\Api\V1\Controllers\BookController@index');
});

We have both routes now! Let’s get back to our HTTP Client and log in again. With our new token, let’s make some requests to store a new book and, right after, get a list of them.

We can pass the token in two ways: passing it as a simple request parameter, or in the header with this specific item: Authorization: Bearer {token_goes_here}.

That said, here’s an example of a store request and the subsequent index one.

Token filled out for storing

… and then …

Index lists out books

It works!

We can finish the job by implementing the three remaining methods: show, update and destroy.

Here they are!

...

public function show($id)
{
    $currentUser = JWTAuth::parseToken()->authenticate();

    $book = $currentUser->books()->find($id);

    if(!$book)
        throw new NotFoundHttpException; 

    return $book;
}

public function update(Request $request, $id)
{
    $currentUser = JWTAuth::parseToken()->authenticate();

    $book = $currentUser->books()->find($id);
    if(!$book)
        throw new NotFoundHttpException;

    $book->fill($request->all());

    if($book->save())
        return $this->response->noContent();
    else
        return $this->response->error('could_not_update_book', 500);
}

public function destroy($id)
{
    $currentUser = JWTAuth::parseToken()->authenticate();

    $book = $currentUser->books()->find($id);

    if(!$book)
        throw new NotFoundHttpException;

    if($book->delete())
        return $this->response->noContent();
    else
        return $this->response->error('could_not_delete_book', 500);
}

...

We continued to use the $currentUser as an implicit “safety check”. In real world cases you should build something more robust.

It’s also possible to throw some Symfony exceptions. The API will automatically recognize them and encode in the right output format. You can find the complete supported exceptions list here.

In show, update and destroy, we used the NotFoundHttpException to tell the client that we have not found a certain resource.

Now, all we have to do is to add the last three new routes to the api_routes.php file.

$api->group(['middleware' => 'api.auth'], function ($api) {
	$api->get('books', 'App\Api\V1\Controllers\BookController@index');
	$api->get('books/{id}', 'App\Api\V1\Controllers\BookController@show');
	$api->post('books', 'App\Api\V1\Controllers\BookController@store');
	$api->put('books/{id}', 'App\Api\V1\Controllers\BookController@update');
	$api->delete('books/{id}', 'App\Api\V1\Controllers\BookController@destroy');
});

Laravel (and Dingo) has the shortcut method resource() that we can use to bind all the routes we just saw with a single instruction.

$api->resource('books', 'App\Api\V1\Controllers\BookController');

Choose the approach you like the most.

The controller is now complete. Here it is in full!

Note: we could stumble upon some issues using PUT and DELETE verbs with some body data. If the body is always empty, try to use x-www-form-urlencoded instead of form-data. It should fix the problem.

The server part is done!

Conclusion

In this part, we focused on the back-end and used a fairly stable and easy to use boilerplate to build our API and authentication + registration flow. Just like that, our app is already client-friendly. Slap together an iOS or Android app or even one of the many JavaScript frameworks and you can consume the content of our application with ease. That’s exactly what we’ll be focusing on in part 2 – building an Angular app for this back end from scratch! Stay tuned!

Sponsors