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');
});