Laravel 5.5 Simple API
laravel5.5

Laravel 5.5 Simple API

Today we are going to build a Laravel 5.5 API, yes you heard right 5.5

composer create-project laravel/laravel book-api  "5.5.*" --prefer-dist

Now edit you .env file

cd book-api
cp .env.example .env

update DB settings

Add your migrations

php artisan make:migration create_user_books_table
php artisan make:migration create_books_table

        Schema::create('books', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title');
            $table->string('original_title');
            $table->smallInteger('publication_year');
            $table->string('isbn');
            $table->string('language_code');
            $table->text('image');
            $table->text('thumbnail');
            $table->smallInteger('average_ratings');
            $table->bigInteger('total_ratings');
            $table->timestamps();
        });
        Schema::create('user_books', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id')->unsigned();
            $table->integer('book_id')->unsigned();
            $table->timestamp('due_at')->nullable();
            $table->timestamp('returned_at')->nullable();
            $table->timestamps();
            $table->foreign('user_id')
                ->references('id')
                ->on('users')
                ->onDelete('cascade')
                ->onUpdate('cascade');
            $table->foreign('book_id')
                ->references('id')
                ->on('books')
                ->onDelete('cascade')
                ->onUpdate('cascade');
        });

BookSeeder

Copy the csv file to the resources folder in your app, remove physically the header row from the csv.

php artisan make:seeder BookSeeder
<?php

use Illuminate\Database\Seeder;
use App\Book;

class BookSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $filePath = base_path("resources/books.csv"); 
        foreach (array_slice(glob($filePath),0,2) as $file) {
             $publication_year = $row[8] != '' ? $row[8] : null;
                    $total_ratings = is_numeric($row[13]) || $row[13] != '' ? $row[13] : null ;
                    $average_ratings = is_numeric($row[12]) || $row[12] != '' ? $row[12] : null ;
                    $title = $row[10] != '' ? $row[10] : null ;

                    $user = Book::create([
                        'title' => $title, 
                        'original_title' => $row[9],
                        'publication_year' => $publication_year,
                        'isbn' => $row[5],
                        'language_code' => $row[11],
                        'image' => $row[21],
                        'thumbnail' => $row[22],
                        'average_ratings' => $average_ratings,
                        'total_ratings' => $total_ratings,
                    ]);
        }
        fclose($handle);
    }
}

Create User seeder

php artisan make:seeder CreateUserSeeder
<?php
  
use Illuminate\Database\Seeder;
use App\User;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
  
class CreateUserSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $user = User::create([
        	'name' => 'Epionet User', 
        	'email' => 'user@epionet.co.za',
        	'password' => bcrypt('12345678')
        ]);
  

    }
}

Errors

php artisan make:migration nullable_year_column_books_table --table=books

Null publication_year in the csv file

<?php

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

class NullableYearColumnBooksTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('books', function (Blueprint $table) {
            $table->string('publication_year')->nullable()->change();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('books', function (Blueprint $table) {
            $table->dropColumn('publication_year');
        });
    }
}
php artisan make:migration nullable_average_ratings_column_books_table --table=books

nullable_average_ratings_column_books_table

<?php

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

class NullableAverageRatingsColumnBooksTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('books', function (Blueprint $table) {
            $table->string('average_ratings')->nullable()->change();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('books', function (Blueprint $table) {
            $table->dropColumn('average_ratings');
        });
    }
}

Might as well create the Models while i am at it

php artisan make:model Book

Run Seeders

php artisan db:seed --class=DatabaseSeeder

Define relationships between models

We are making use of a pivot table, that is UserBook

A user can add as many books as they wish, but a book can only belong to one user. So, the relationship between the User model and Book model is a belongs-to-many relationship

/**
     * DB relationship User to Books.
     *
     * @return 
     */
    public function books()
    {
            return $this->hasMany(Book::class)
            ->withTimestamps()
            -withPivot(['due_at', 'returned_at']);
    }
<?php

namespace App;

use App\User;

use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
    //
}
return $this->hasMany(User::class);

JWT authentication

In your terminal run this command to instal JWT 1.0.0-rc.3 for your laravel 5.5

composer require tymon/jwt-auth "1.0.0-rc.3"

 generate a secret key

 php artisan jwt:secret
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

You need to implement the JWT contract is the User model

  use Tymon\JWTAuth\Contracts\JWTSubject;

    class User extends Authenticatable implements JWTSubject

In your User model add these two methods

    /**
     * getJWTIdentifier.
     * gets identifier 
     * @return 
     */
    public function getJWTIdentifier()
    {
      return $this->getKey();
    }

    /**
     * getJWTCustomClaims.
     *
     * @return 
     */
    public function getJWTCustomClaims()
    {
      return [];
    }
      

Add the below code in config/app.php

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

Add this code in Kernel.php

'jwt.auth' => \Tymon\JWTAuth\Http\Middleware\Authenticate::class,
'jwt.refresh' => \Tymon\JWTAuth\Http\Middleware\RefreshToken::class,

Change auth guard to use  jwt guard. Update config/auth.php 

    'guards' => [
      'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
      ],
    ],

UserController

php artisan make:controller UserController

 add the register and login routes.

Route::post('register', 'UserController@register');
Route::post('login', 'UserController@login');

Lets try these endpoints in postman

Resources collections

php artisan make:resource BookCollection

Restful controllers

php artisan make:controller API/BookController --resource

Service Providers

php artisan make:provider LibraryServiceProvider
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\MyLibraryServices\MyLibrary;

class LibraryServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        app()->singleton(MyLibrary::class, function(){  
            return new MyLibrary;
        });
  
    }
}

MyLibrary Class

<?php

namespace App\MyLibraryServices;
use Carbon\Carbon;
use App\Book;
use App\User;

/**
* Library service class to process checking out and return of books
*/
class MyLibrary 
{
    /**
     * processes checking out of book.
     *
     * @param  int $user
     * @param  int $bookcheckOutBook
     * @return \Illuminate\Http\Response
     */
    public function checkOutBook($user, $book)
    {
        $current = Carbon::now();
        //this can be parameterised
        $dueAt = $current->addDays(7);

        $userData = User::find($user);
        $bookData = Book::find($book);

        $userData->books()->attach($bookData, [
                                'due_at'=> $dueAt, 
                                'returned_at'=>null
                                ]);

        return response()->json([
            'success' => true
        ]);
    }

    /**
     * Processes return of book
     *
     * @param  int $user
     * @param  int $book
     * @return \Illuminate\Http\Response
     */
    public function returnBook($user, $book)
    {
               
        $userData = User::find($user);
        $bookData = Book::find($book);
        
        $userData->books()->attach($bookData, [
                                'due_at'=> null,
                                'returned_at'=>now()
                                ]);

        return response()->json([
            'success' => true
        ]);
    }
}

Now update the BookController

<?php

namespace App\Http\Controllers\API;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Resources\BookCollection;
use App\Book;
use App\User;

class BookController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {

        return new BookCollection(Book::all());

    }

     /**
     * process book given the  specified process_type .
     *
     * @param  \Illuminate\Http\Request  $request 
     * @return \Illuminate\Http\Response
     */
    public function processbook(Request $request)
    {
        $this->validate($request,[
            'book_id' => 'required|max:50',
            'user_id' => 'required|max:50',
            'process_type' => 'required|max:20',
        ]);
        //further validation
        if (!User::find($request['user_id'])) {
            return response()->json([
                'success' => false,
                'message' => 'Sorry, user with id ' . $request['user_id'] 
                . ' cannot be found'
            ], 400);
        }
        if (!Book::find($request['book_id'])) {
            return response()->json([
                'success' => false,
                'message' => 'Sorry, book with id ' . $request['book_id'] 
                . ' cannot be found'
            ], 400);
        }

        $book = resolve('App\MyLibraryServices\MyLibrary');
        if ($request['process_type'] == "checkout") {
            return $book->checkOutBook($request['user_id'], $request['book_id']);

        } elseif ($request['process_type'] == "return") {
            return $book->returnBook($request['user_id'], $request['book_id']);

        } else {
            return response()->json([
                'success' => false,
                'message' => 'Sorry, book with process_type ' 
                . $request['process_type'] . ' cannot be found'
            ], 400);
        }
    }
    
}

Update API Routes

<?php

use Illuminate\Http\Request;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::post('register', 'API\UserController@register');
Route::post('login', 'API\UserController@login');

Route::group(['middleware' => 'jwt.auth'], function () {
    Route::get('logout', 'API\UserController@logout');
    Route::get('user', 'API\UserController@me');
    Route::apiResource('books', 'API\BookController');
    Route::post('processbook', 'API\BookController@processbook');
});

Run App

Leave a Reply

Close Menu