Baljeet Singh

16 minute read

https://github.com/mbaljeetsingh/laravel5-jokes-api-with-jwt-and-pagination

Laravel & Angularjs works really well together, if used in a correct way. In this Laravel & Angularjs Series, we will be creating an app where Laravel will be used for creating an API and Angularjs for consuming that API.

This series is aimed at those, who want to completely separate their frontend and backend. This is the most common use case in hybrid app development using phonegap or ionic framework. So without further due, let’s get started with the Laravel Part.

Install Laravel & Create New Project

First Install Composer By Following this guide. Then, download the Laravel installer using Composer.

composer global require "laravel/installer=~1.1"

Once successfully installed, you can type laravel new command to create new Laravel project.

laravel new project_name

After successfully creating the project, make sure your webserver is running (you can use XAMPP, LAMP, MAMP), now go into the project directory and type.

php artisan serve

Now you can open the following url.

http://localhost:8000 And you will see the Laravel front page.

Now we will add a package called Laravel-5-Generators. This Laravel package provides a variety of generators to speed up your development process.

You can install the generator via composer as:

composer require laracasts/generators --dev

Now open the app/Providers/AppServiceProvider.php file and updated the register function as,

public function register()
{
    if ($this->app->environment() == 'local') {
        $this->app->register('Laracasts\Generators\GeneratorsServiceProvider');
    }
}

Now run,

php artisan

You will see new commands added in the make:* section.

Setup DB, Create Migration & Insert Dummy Data

Setup DB

First, rename the .env.example file in the laravel installation root directory to .env

now open the .env file and change the following:-

DB_HOST=localhost DB_DATABASE= your_database_name DB_USERNAME= your_database_username DB_PASSWORD= your_database_password

Create Migration

Next step is to create the migration, as the laravel documentation says, Migrations are like version control for your database, allowing a team to easily modify and share the application’s database schema. Open the terminal and type the following command to create the migration.

php artisan make:migration create_jokes_table

You can see the created migration in database/migrations folder. Open the migration file and replace it with this code.

<?php

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

class CreateJokesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('jokes', function (Blueprint $table) {
            $table->increments('id');
            $table->text('joke');
            $table->integer('user_id');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('jokes');
    }
}

Here you can see we have added the id, joke, user_id and timestamps fields. Where id is auto incrementing, joke is the body of the joke and user_id will be used for the user who will submit the joke. Here there are two functions one is up and other is down. The down function is the exact opposite of the up and will be used if we want to revert back the migration.

Now run the following command

php artisan migrate

Now have a look at the database, you can see the new jokes table there. Along with jokes table, laravel creates three other tables namely users, migrations, password_resets.

Now to demonstrate the down function. Let’s say we want to change the joke field with body. What you can do is, change the field in the migration table.

 public function up()
    {
        Schema::create('jokes', function (Blueprint $table) {
            $table->increments('id');
            $table->text('body');
            $table->integer('user_id');
            $table->timestamps();
        });
    }

Now run the following commands.

php artisan migrate:rollback
php artisan migrate

Now open the database and check the structure and you can see the jokes table joke field renamed to body.

Insert Dummy Data

The next step is to insert the dummy data that we can use throughout the development process. For inserting dummy data we will use a package called fzaninotto/faker.

First we need to install this package,

composer require fzaninotto/faker

Now first create a Joke Model by typing:

php artisan make:model Joke

Now create the seed file which will be used to insert dummy data.

php artisan make:seed Jokes

You can see the created seeder file in database/seed folder. Open JokesTableSeeder.php and add the following.

<?php

use Illuminate\Database\Seeder;
use App\Joke;

class JokesTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $faker = Faker\Factory::create();

        foreach(range(1,30) as $index)
        {
            Joke::create([
                'body' => $faker->paragraph($nbSentences = 3),
                'user_id' =>$faker->numberBetween($min = 1, $max = 5)
            ]);
        }
    }
}

At the top you can see, we have added App\Joke that makes us available the jokes model and we are using to create new jokes as Joke::create. Here you can see we have used $faker object, it is availabe through package. For the list of all the available functions, you can have a look at their documentation.

Now Create the new seed file for users table as.

php artisan make:seed UsersTableSeeder

Now open UsersTableSeeder.php and add the following.

<?php

use Illuminate\Database\Seeder;
use App\User;

class UsersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $faker = Faker\Factory::create();

        foreach(range(1,5) as $index)
        {
            User::create([
                'name' => $faker->userName,
                'email' =>$faker->email,
                'password' =>bcrypt('secret')
            ]);
        }
    }
}

Here you can see we have added App\User at the top, which means we are requiring user model. It is availabe through default laravel install.

The next step is to open DatabaseSeeder.php and add the created seeder classes.

<?php

use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;

class DatabaseSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Model::unguard();

        $this->call(JokesTableSeeder::class);
        $this->call(UsersTableSeeder::class);

        Model::reguard();
    }
}

Now before using the seed command we need to do one more thing. Open the Joke Model file. This will be inside app root folder and add the fillable array inside the class as.

protected $fillable = ['body', 'user_id'];

By using this we can mass insert values in the database table.

Now the last step is to run the db seed command as.

php artisan migrate --seed

After that you can see the database table with all the dummy entries.

Creating & Testing Routes

In the last section we successfully created the database, created migration and prepared seeder classes. In this section we will create our routes or api end points which we can hit to get data.

Open the routes.php file and add the follwing code:

Route::resource('jokes', 'JokesController');

Now we need to create the JokesController, for this we will use generators like,

php artisan make:controller JokesController

This command will create the jokescontroller with all the required methods.

Now run this command:

php artisan route:list

You will see output similar to this:

null

Now we can add the prefix to all these routes as, in the routes.php updates the code as: Route::group([‘prefix’ => ‘api/v1’], function(){ Route::resource(‘jokes’, ‘JokesController’); }); Now your output will look like:

null Now open the JokesController.php, add the following at the top,

use App\Joke;

Now update the index method as:

public function index(){
    $jokes = Joke::all(); //Not a good idea
    return $jokes;
}

Here we are using the Eloquent ORM, which makes it easy to work with database. If you don’t know about eloquent, have a look at the Eloquent Documentation.

The Joke::all() will return all the rows from the jokes table.

Now run the server as:

php artisan serve

and open:

http://localhost:8000/api/v1/jokes

You will see all the records from the jokes table, also it is worth mentioning, the data is casted to json automatically.

null

Now using the browser is good if you want to test GET request, but if you want to test POST, PUT, PATCH, DELETE request we probably will not use browser for this. So, for that scenario we can use Curl, which is perfectly fine or we can use a utility called POSTMAN which is a chrome extension. That makes it really easy to test all the requests. Let’s see how we can test our api with postman. Install the postman extension and open postman:

null

Here select the request (GET, POST etc.), enter url and press send, the output is similar to the one we saw in browser but this will prove more useful once we get to other requests.

Now, yes it is a working api but there are some problems with it:

  • It is not a good idea to return all the results at once.
  • We are showing the exact structure of the table.
  • We are sending any error message and response codes.

Now in the following sections, we will work on improving that api.

Responses & Codes

Now let’s add the status codes to the data, the available status codes are:

null

Updated the index method in the JokesController.php as follows:

public function index(){
    $jokes = Joke::all();
    return Response::json([
         'message' => $jokes
    ], 400);
}

Now open the /api/v1/jokes url in the postman, you can see status returned as 400.

The 400 is mainly for the bad request, so the status that we probably want to return is 200, so change the code as follows:

public function index(){
    $jokes = Joke::all();
    return Response::json([
         'data' => $jokes
    ], 200);
}

Now again hit the endpoint/url in the postman, you can see status returned as 200.

Now let’s implement the route for getting single joke, we can see from php artisan route:list, when we get to api/v1/jokes/{jokes} we are calling the show method and will get single joke.

So update the show method as follows:

public function show($id){
        $joke = Joke::find($id);

        if(!$joke){
            return Response::json([
                'error' => [
                    'message' => 'Joke does not exist'
                ]
            ], 404);
        }

        return Response::json([
                'data' => $joke
        ], 200);
}

Now try accessing this route, if the joke with specific id not exists we will get the error response and if it exists we will get the 200 response.

If the joke exists:

null

If the joke doesn’t exists:

null

Transforming Data

At that point, we can see that, we are returning the exact db table structure, and it is not a good idea, it may be possible we only want to show specific fields and don’t want to show the fields name as in the table. So for this we can transform the data before displaying.

Add the following two methods at the bottom of the JokesController Class in the JokesController.php,

private function transformCollection($jokes){
    return array_map([$this, 'transform'], $jokes->toArray());
}

private function transform($joke){
    return [
           'joke_id' => $joke['id'],
           'joke' => $joke['body']
        ];
}

Now update the index and show methods as:

public function index(){
        $jokes = Joke::all();
        return Response::json([
                'data' => $this->transformCollection($jokes)
        ], 200);
}

public function show($id){
        $joke = Joke::find($id);

        if(!$joke){
            return Response::json([
                'error' => [
                    'message' => 'Joke does not exist'
                ]
            ], 404);
        }

        return Response::json([
                'data' => $this->transform($joke)
        ], 200);
}

Now again hit the routes and you will see the transformed data.

Eloquent Relationships

Using Eloquent Relationships we can relate two tables.

First we need to define the relationship between the two tables users and jokes and then we can join the tables data.

Let’s start by adding a relationship:

open the User.php model file and add the following method:

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

and open the Joke.php model file and ad the following method:

public function user(){
        return $this->belongsTo('App\User');
    }

Now open the JokesController.php and update the show method as follows, before that add the following code at the top:

use App\User;

Now update the show method as:

public function show($id)
    {
        $joke = Joke::with(
            array('User'=>function($query){
                $query->select('id','name');
            })
            )->find($id);

        if(!$joke){
            return Response::json([
                'error' => [
                    'message' => 'Joke does not exist'
                ]
            ], 404);
        }

         // get previous joke id
        $previous = Joke::where('id', '<', $joke->id)->max('id');

        // get next joke id
        $next = Joke::where('id', '>', $joke->id)->min('id');

        return Response::json([
            'previous_joke_id'=> $previous,
            'next_joke_id'=> $next,
            'data' => $this->transform($joke)
        ], 200);
    }

and our updated transform method with look like this:

private function transform($joke){
        return [
                'joke_id' => $joke['id'],
                'joke' => $joke['body'],
                'submitted_by' => $joke['user']['name']
        ];
    }

So, our updated show method code will look like this, here first we are joining User table data with the jokes tables, also we are finding the previous and next joke and then we are adding them to the response. So our response will now look like this:

null

Where submitted_by data is coming from users table after joining.

Implement POST, PUT, DELETE requests

Open JokesController.php and update the store, update and destroy methods as follows:

First add the following code at the top of the file,

use Illuminate\Http\Request;

Now update the following methods:

    public function store(Request $request)
    {

        if(! $request->body or ! $request->user_id){
            return Response::json([
                'error' => [
                    'message' => 'Please Provide Both body and user_id'
                ]
            ], 422);
        }
        $joke = Joke::create($request->all());

        return Response::json([
                'message' => 'Joke Created Succesfully',
                'data' => $this->transform($joke)
        ]);
    }
public function update(Request $request, $id)
    {
        if(! $request->body or ! $request->user_id){
            return Response::json([
                'error' => [
                    'message' => 'Please Provide Both body and user_id'
                ]
            ], 422);
        }

        $joke = Joke::find($id);
        $joke->body = $request->body;
        $joke->user_id = $request->user_id;
        $joke->save();

        return Response::json([
                'message' => 'Joke Updated Succesfully'
        ]);
    }
   public function destroy($id)
    {
        Joke::destroy($id);
    }

To test POST request, open postman and send post request as follows:

null

Similarly, you can test Update and Delete requests.

Pagination

For pagination, Laravel provides a very simple method called paginate(), let’s see how we can implement pagination. Open JokesController.php and update the index method as follows:

   public function index(){
        $jokes = Joke::with(
                array('User'=>function($query){
                    $query->select('id','name');
                })
                )->select('id', 'body', 'user_id')->paginate(5);
        return Response::json($this->transformCollection($jokes), 200);
    }

Now access the route, you will see error something like this,

The problem is now we need to update the transformcollection method in the JokesController.php as:

    private function transformCollection($jokes){
        $jokesArray = $jokes->toArray();
        return [
            'total' => $jokesArray['total'],
            'per_page' => intval($jokesArray['per_page']),
            'current_page' => $jokesArray['current_page'],
            'last_page' => $jokesArray['last_page'],
            'next_page_url' => $jokesArray['next_page_url'],
            'prev_page_url' => $jokesArray['prev_page_url'],
            'from' => $jokesArray['from'],
            'to' =>$jokesArray['to'],
            'data' => array_map([$this, 'transform'], $jokesArray['data'])
        ];
    }

Now go to the route you will see the paginated result, like this.

null

Currently, we are hard coding the pagination to 5 elements but we can use query string to specify the no. of elements. The updated index method will look like this:

public function index(Request $request)
    {

        $limit = $request->input('limit')?$request->input('limit'):5;

        $jokes = Joke::with(
        array('User'=>function($query){
            $query->select('id','name');
        })
        )->select('id', 'body', 'user_id')->paginate($limit);

        $jokes->appends(array(
            'limit' => $limit
        ));

        return Response::json($this->transformCollection($jokes), 200);
    }

Here we are checking to see, if the limit is present as query string, if yes use that value otherwise the no. of elements are default to 5.

Now let’s implement the search functionality, update the index method as follows:

    public function index(Request $request)
    {
        $search_term = $request->input('search');
        $limit = $request->input('limit')?$request->input('limit'):5;

        if ($search_term)
        {
            $jokes = Joke::orderBy('id', 'DESC')->where('body', 'LIKE', "%$search_term%")->with(
            array('User'=>function($query){
                $query->select('id','name');
            })
            )->select('id', 'body', 'user_id')->paginate($limit);

            $jokes->appends(array(
                'search' => $search_term,
                'limit' => $limit
            ));
        }
        else
        {
            $jokes = Joke::orderBy('id', 'DESC')->with(
            array('User'=>function($query){
                $query->select('id','name');
            })
            )->select('id', 'body', 'user_id')->paginate($limit);

            $jokes->appends(array(
                'limit' => $limit
            ));
        }

        return Response::json($this->transformCollection($jokes), 200);
    }

Now you can test the search as follows:

null

Adding JWT Auth

Now we will add the security to the routes, such that only the authenticated users can request the data, otherwise they will get error.

Let’s start with basic auth, that laravel provides by default, open the JokesController.php and add the contructor function as:

   public function __construct(){
        //$this->middleware('auth.basic', ['only' => 'store']);
        $this->middleware('auth.basic');
    }

This means we are using basic auth on all routes. When we now try to view the route in the browser, we will first need to signin and then we see the data like.

null

Now, if we type the correct credentials, only then we can see the data.

But we won’t be using this for our api, for api’s it is better to use token based authentication, which means whenever the user authenticates he will be provided with the token, and the user will then send this token with each request and if the token is valid, only then the data will be displayed.

For Token Based Auth, we will use a laravel package called jwt-auth ,

First open the composer.json and add the following item to the required array,

"tymon/jwt-auth": "0.5.*"

Now run the following command:

composer update

It will download the jwt-auth package.

Now Open config/app.php file and add the following item to the providers array:

Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class

Also in the config/app.php, add the following items to the aliases array:

'JWTAuth'   => Tymon\JWTAuth\Facades\JWTAuth::class,
'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class

Now publish this package from command line as:

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"

Now you will see a new file in the config folder called jwt.php, Now we need to generate jwt specific key from command line as:

php artisan jwt:generate

Now you can see the key in the secret field changed to new key.

Now open the routes.php and add the following routes:

Route::group(['prefix' => 'api/v1'], function()
{
    Route::resource('authenticate', 'AuthenticateController', ['only' => ['index']]);
    Route::post('authenticate', 'AuthenticateController@authenticate');
    Route::get('authenticate/user', 'AuthenticateController@getAuthenticatedUser');
});

Add the following middleware to the construct method in the JokesController.php:

public function __construct(){
        $this->middleware('jwt.auth');
}

Now we need to make the AuthenticateController from the command line as:

php artisan make:controller AuthenticateController

Now open the AuthenticateController.php and update the file as follows:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\User;
use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;

class AuthenticateController extends Controller
{

    public function __construct()
   {
       $this->middleware('jwt.auth', ['except' => ['authenticate']]);
   }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        return "Auth index";
    }

    public function authenticate(Request $request)
    {
        $credentials = $request->only('email', 'password');

        try {
            // verify the credentials and create a token for the user
            if (! $token = JWTAuth::attempt($credentials)) {
                return response()->json(['error' => 'invalid_credentials'], 401);
            }
        } catch (JWTException $e) {
            // something went wrong
            return response()->json(['error' => 'could_not_create_token'], 500);
        }

        // if no errors are encountered we can return a JWT
        return response()->json(compact('token'));
    }

    public function getAuthenticatedUser()
    {
        try {

            if (! $user = JWTAuth::parseToken()->authenticate()) {
                return response()->json(['user_not_found'], 404);
            }

        } catch (Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {

            return response()->json(['token_expired'], $e->getStatusCode());

        } catch (Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {

            return response()->json(['token_invalid'], $e->getStatusCode());

        } catch (Tymon\JWTAuth\Exceptions\JWTException $e) {

            return response()->json(['token_absent'], $e->getStatusCode());

        }

        // the token is valid and we have found the user via the sub claim
        return response()->json(compact('user'));
    }
}

Now try to hit the route, /api/v1/jokes, you will get an error token not provided like this:

Which means, we first need to generate token that we can generate by sending post request to api/v1/authenticate with email and password as following:

Now you can send the requests with this token and then you will get the result as expected.

Tackling CORS

Now our api is working fine, but there is last thing to take care of, now let’s say if we put this api online, no one will be able to access the api due to cross domain restrictions. So to overcome this we can install a Laravel package called laravel-cors.

First install the package using composer:

composer require barryvdh/laravel-cors 0.7.x

Now add this package to config/app.php file’s providers array as:

Barryvdh\Cors\ServiceProvider::class

Now to use this package, open the routes file and update the routes as follows:

Route::group(['middleware' => 'cors', 'prefix' => 'api/v1'], function()
{
    Route::resource('authenticate', 'AuthenticateController', ['only' => ['index']]);
    Route::post('authenticate', 'AuthenticateController@authenticate');
    Route::get('authenticate/user', 'AuthenticateController@getAuthenticatedUser');
});

Route::group(['middleware' => 'cors', 'prefix' => 'api/v1'], function(){
	Route::resource('jokes', 'JokesController');
});

Wrapping Up Part 1

Hopefully this tutorial was helpful and you have created your first API by the end of it.

Feel free to leave any feedback or questions in the comments below and let me know if there’s anything you need help with or if I can clarify anything.

Thanks @jeffrey_way (laracasts) for his awesome screencasts, learned lot from him & jwt-auth inspiration by @ryanchenkie.

See Part 2 of this series, where we will create an app with AngularJS using this API.

Thanks.

comments powered by Disqus