REST API 
Development and 
Testing 101 
By Samantha Geitz
A bit about me 
• 3 years of experience in web development 
• Now work for Packback 
• Background mostly in WordPress, a little bit of 
Rails, now use Laravel full-time 
• About 1 year experience building APIs
What we’re going to talk 
about tonight 
• “Typical” Laravel application / MVC 
• What is an API, and why should you care? 
• Shitty way to structure an API 
• Better way to structure an API 
• Demonstration of interacting with API
Our Application 
• Packback 
• Digital textbook rentals for 
college students 
• Resources: Users, Books 
• Users need to be able to “rent” 
books 
• https://github.com/samanthamic 
hele7/packback-rest-api-101 
• Two branches: master and 
api_with_fractal
Anatomy of the“Typical” 
Laravel Application 
• Model (Eloquent) + database 
• Controller + routes 
• View (Blade)
Problems! 
• What happens if we want to build an iOS/Android 
application with our data? 
• What happens if we want to use AngularJS or 
EmberJS? 
• What happens when we want to rebuild the front-end 
in a few years? 
• What happens if we want to let other companies work 
with our data? (Twitter API, Facebook API, etc.)
Solution: 
Let’s build an API! 
(Application Programming Interface)
What the hell does that even mean? 
• APIs only care about data - not what things look like 
• Data in, data out 
• JSON is what most of the cool kids are using 
• Keeps data types intact 
• You can also use XML if you like typing a lot 
• Turns everything into a string
<book> 
<id>1</id> 
<title>The Lord of the Rings</title> 
<author>J. R. R. Tolkien</author> 
</book> 
XML Example 
{ 
"book": { 
"id" : 1, 
"title": "The Lord of the Rings", 
"author": "J. R. R. Tolkien" 
} 
} 
JSON Example
Laravel makes it really easy 
• The client can access routes (which are basically just URLs) 
• Controllers handle logic (or call other classes to handle it for 
them) 
• Get data from or store data in database (via models) 
• ????? 
• PROFIT!!! 
• Return data as JSON
Stuff that we need to do 
• /createUser 
• /fetchUser 
• /setUserPassword 
• /updatePaymentInfo 
• /addBook 
• /getBook 
• /deleteBook 
• /addBooktoUser 
• /removeBook
Okay, this is getting 
kind of confusing
REST to the Rescue! 
• Representational State Transfer 
• Hopefully not what you’re doing if I’m boring you
It does CRUD 
• C - Create (POST) 
• R - Read (GET) 
• U - Update (PATCH/PUT) 
• D - Destroy (DELETE)
Stuff that we need to do 
(the RESTful way) 
• POST /users - Create a new user 
• GET /users - Get all users 
• GET /users/{id} - Get one user by ID 
• PATCH /users/{id} - Update a user by ID 
• DELETE /users/{id} - Delete a user by ID 
• POST /users/{id}/books/{id} - Add a book (or books) to a user 
• GET /users/{id}/books/ - Get a list of books for a specific user 
• etc.
Cool story, bro, but how 
do I actually build an API?
Step 1: Create Databases 
• Run database migrations (the same way as in a regular 
Laravel application) 
• Books, users, pivot 
php artisan migrate
app/database/migrations/2014_11_18_024437_create_users_table.<?php 
use IlluminateDatabaseSchemaBlueprint; 
use IlluminateDatabaseMigrationsMigration; 
class CreateUsersTable extends Migration { 
/** 
* Run the migrations. 
* 
* @return void 
*/ 
public function up() 
{ 
Schema::create('users', function(Blueprint $table) { 
$table->increments('id'); 
$table->string('email'); 
$table->string('name'); 
$table->string('password'); 
$table->timestamps(); 
}); 
} 
/** 
* Reverse the migrations. 
* 
* @return void 
*/ 
public function down() 
{ 
Schema::drop('users'); 
} 
}
app/database/migrations/2014_11_18_024939_create_books_table.<?php 
use IlluminateDatabaseSchemaBlueprint; 
use IlluminateDatabaseMigrationsMigration; 
class CreateBooksTable extends Migration { 
/** 
* Run the migrations. 
* 
* @return void 
*/ 
public function up() 
{ 
Schema::create('books', function(Blueprint $table) { 
$table->increments('id'); 
$table->string('isbn13'); 
$table->string('title'); 
$table->string('author'); 
$table->float('price'); 
$table->timestamps(); 
}); 
} 
/** 
* Reverse the migrations. 
* 
* @return void 
*/ 
public function down() 
{ 
Schema::drop('books'); 
} 
}
database/migrations/2014_11_18_025038_create_books_users_<?php 
use IlluminateDatabaseSchemaBlueprint; 
use IlluminateDatabaseMigrationsMigration; 
class CreateBooksUsersTable extends Migration { 
/** 
* Run the migrations. 
* 
* @return void 
*/ 
public function up() 
{ 
Schema::create('books_users', function(Blueprint $table) { 
$table->increments('id'); 
$table->integer('user_id')->unsigned(); 
$table->foreign('user_id')->references('id')->on('users'); 
$table->integer('book_id')->unsigned(); 
$table->foreign('book_id')->references('id')->on('books'); 
$table->timestamps(); 
}); 
} 
/** 
* Reverse the migrations. 
* 
* @return void 
*/ 
public function down() 
{ 
Schema::table('books_users', function(Blueprint $table) { 
$table->dropForeign('books_users_user_id_foreign'); 
$table->dropForeign('books_users_book_id_foreign'); 
}); 
Schema::drop('books_users'); 
}
Step 2: Seed data 
• Your life will be much easier if you fill your database with fake 
data 
• Faker is easy to use and has realistic fake data: 
https://github.com/fzaninotto/Faker 
• I generally do one seed file per table 
• Hook in database/seeds/DatabaseSeeder.php 
• Make sure you truncate every time the seeder is run or you will 
end up with a ton of data 
php artisan db:seed
Seed Users 
app/database/seeds/UserTableSeeder.php 
<?php 
use CarbonCarbon; 
use FakerFactory as Faker; 
class UserTableSeeder extends Seeder 
{ 
public function run() 
{ 
$faker = Faker::create(); 
DB::table('users')->truncate(); 
for ($i = 0; $i < 50; $i++) { 
DB::table('users')->insert([ 
'email' => $faker->email, 
'name' => $faker->name, 
'password' => Hash::make($faker->word), 
'created_at' => Carbon::now(), 
'updated_at' => Carbon::now() 
]); 
} 
} 
}
Seed Books 
app/database/seeds/BookTableSeeder.php 
<?php 
use CarbonCarbon; 
use FakerFactory as Faker; 
class BookTableSeeder extends Seeder 
{ 
public function run() 
{ 
$faker = Faker::create(); 
DB::table('books')->truncate(); 
for ($i = 0; $i < 50; $i++) { 
DB::table('books')->insert([ 
'isbn13' => $faker->ean13(), 
'title' => $faker->sentence, 
'author' => $faker->name, 
'price' => $faker->randomNumber(2) . '.' . $faker->randomNumber(2), 
'created_at' => Carbon::now(), 
'updated_at' => Carbon::now(), 
]); 
} 
} 
}
Seed User Books 
app/database/seeds/UserBookTableSeeder.php 
<?php 
use CarbonCarbon; 
class UserBookTableSeeder extends Seeder 
{ 
public function run() 
{ 
DB::table('books_users')->truncate(); 
for ($i = 1; $i < 51; $i++) { 
DB::table('books_users')->insert([ 
[ 
'user_id' => $i, 
'book_id' => $i, 
'created_at' => Carbon::now(), 
'updated_at' => Carbon::now() 
], 
[ 
'user_id' => $i, 
'book_id' => 51 - $i, 
'created_at' => Carbon::now(), 
'updated_at' => Carbon::now() 
] 
]); 
} 
} 
}
Step 3: Models 
• Very little difference compared to a more traditional 
Laravel app 
• Define a ManyToMany relationship between users and 
books
app/models/User.php 
class User extends Eloquent implements UserInterface, RemindableInterface { 
use UserTrait, RemindableTrait; 
protected $table = 'users'; 
protected $fillable = ['email', 'name', 'password']; 
protected $hidden = array('password', 'remember_token'); 
public function books() 
{ 
return $this->belongsToMany('Book', 'books_users'); 
} 
public function setPasswordAttribute($password) 
{ 
$this->attributes['password'] = Hash::make($password); 
} 
}
app/models/Book.php 
class Book extends Eloquent { 
protected $table = 'books'; 
protected $fillable = ['isbn13', 'title', 'author', 'price']; 
public function users() 
{ 
return $this->belongsToMany('User', 'books_users'); 
} 
}
Step 4: Routes 
• Should you use Laravel magic? (Route::resource() or 
Route::controller()) 
• Pros: Less code 
• Cons: Less code 
• It is generally clearer (to me) to explicitly define your 
routes (so you have a blueprint) 
• However, some people would disagree, so we’ll look 
at both
RESTful routes 
Option 1 (Less code) 
Route::group(['prefix' => 'api'], function() { 
Route::resource('users', 'UserController'); 
Route::resource('books', 'BookController'); 
}); 
This will automatically look for create, edit, 
index, show, store, update, and destroy 
methods in your controller.
RESTful Routes 
Option 2 (Explicit code) 
Route::group(['prefix' => 'api'], function() { 
Route::group(['prefix' => 'books'], function(){ 
Route::get('', array('uses' => 'BookController@index')); 
Route::get('{book_id}', array('uses' => 'BookController@show')); 
Route::post('', array('uses' => 'BookController@store')); 
Route::patch('{book_id}', array('uses' => 'BookController@update')); 
Route::delete('{book_id}', array('uses' => 'BookController@destroy')); 
}); 
Route::group(['prefix' => 'users'], function(){ 
Route::get('', array('uses' => 'UserController@index')); 
Route::get('{user_id}', array('uses' => 'UserController@show')); 
Route::get('{user_id}/books', array('uses' => 'UserController@showBooks')); 
Route::post('', array('uses' => 'UserController@store')); 
Route::post('{user_id}/books/{book_id}', array('uses' => 'UserController@storeBooks')); 
Route::patch('{user_id}', array('uses' => 'UserController@update')); 
Route::delete('{user_id}', array('uses' => 'UserController@destroy')); 
Route::delete('{user_id}/books/{book_id}', array('uses' => 'UserController@destroyBooks')); 
}); 
});
Let’s talk about status codes 
• Your API needs to send back a HTTP status code 
so that the client knows if the succeeded or failed 
(and if it failed, why) 
• 2xx - GREAT SUCCESS 
• 3xx - Redirect somewhere else 
• 4xx - Client errors 
• 5xx - Service errors
Some common status codes 
• 200 - generic OK 
• 201 - Created OK 
• 301 - Moved permanently and redirect to new location 
• 400 - Generic bad request (often used for validation on models) 
• 401 - Unauthorized (please sign in) 
• 403 - Unauthorized (you are signed in but shouldn’t be accessing this) 
• 404 - Does not exist 
• 500 - API dun goofed 
• 503 - API is not available for some reason 
• Plus lots more!
Step 5: Controllers 
• You will need (at least) 5 methods: index (get all), show (get 
one), store, update, destroy 
• What about create() and edit() (if you use 
Route::resource())? 
• You don’t need them if you’re building a pure data-driven 
API! 
• Use Postman (http://www.getpostman.com/) to interact 
with your API instead of Blade templates
Controllers / Index 
/** 
* Get all books 
* 
* @return Response 
*/ 
public function index() 
{ 
$books = Book::all(); 
return Response::json([ 
'data' => $books 
]); 
} 
/** 
* Get all users 
* 
* @return Response 
*/ 
public function index() 
{ 
$users = User::all(); 
return Response::json([ 
'data' => $users 
]); 
}
Controllers / Show 
/** 
* Get a single user 
* 
* @param $user_id 
* @return Response 
*/ 
public function show($user_id) 
{ 
$user = User::findOrFail($user_id); 
return Response::json([ 
'data' => $user 
]); 
} 
/** 
* Get a single book 
* 
* @param $book_id 
* @return Response 
*/ 
public function show($book_id) 
{ 
$book = Book::findOrFail($book_id); 
return Response::json([ 
'data' => $book 
]); 
}
Controllers / Store 
/** 
* Store a book 
* 
* @return Response 
*/ 
public function store() 
{ 
$input = Input::only('isbn13', 'title', 'author', 
'price'); 
$book = Book::create($input); 
return Response::json([ 
'data' => $book 
]); 
} 
/** 
* Store a user 
* 
* @return Response 
*/ 
public function store() 
{ 
$input = Input::only('email', 'name', 
'password'); 
$user = User::create($input); 
return Response::json([ 
'data' => $user 
]); 
}
Controllers / Update 
/** 
* Update a book 
* 
* @param $book_id 
* @return Response 
*/ 
public function update($book_id) 
{ 
$input = Input::only('isbn13', 'title', 
'author', 'price'); 
$book = Book::find($book_id); 
$book->update($input); 
return Response::json([ 
'data' => $book 
]); 
} 
/** 
* Update a user 
* 
* @param $user_id 
* @return Response 
*/ 
public function update($user_id) 
{ 
$input = Input::only('email', 'name', 
'password'); 
$user = User::findOrFail($user_id); 
$user->update($input); 
return Response::json([ 
'data' => $user 
]); 
}
Controllers / Destroy 
/** 
* Delete a book 
* 
* @param $book_id 
* @return Response 
*/ 
public function destroy($book_id) 
{ 
$book = User::findOrFail($book_id); 
$book->users()->sync([]); 
$book->delete(); 
return Response::json([ 
'success' => true 
]); 
} 
/** 
* Delete a user 
* 
* @param $user_id 
* @return Response 
*/ 
public function destroy($user_id) 
{ 
$user = User::findOrFail($user_id); 
$user->books()->sync([]); 
$user->delete(); 
return Response::json([ 
'success' => true 
]); 
}
Postman 
Demonstration
Let’s Review! 
• Request is sent through client (we used Postman, but could 
be AngularJS app, iPhone app, etc.) 
• Route interprets where it needs to go, sends it to 
appropriate controller + method 
• Controller takes the input and figures out what to do with it 
• Model (Eloquent) interacts with the database 
• Controller returns the data as JSON 
• Look, ma, no views!
A few problems… 
• We’re relying on the Laravel “hidden” attribute to avoid 
showing sensitive information but otherwise have no 
control over what is actually output. This is dangerous. 
• What happens if our database schema changes? 
• For example, we need to add a daily vs semester 
rental price and rename the “price” database column 
• How can we easily show a user + associated books with 
one API call?
Use transformers!
Transformers 
(Not like the robots) 
• “Transform” data per resource so that you have a lot more 
control over what you’re returning and its data type 
• Easy to build your own, or you can use Fractal for more 
advanced features: http://fractal.thephpleague.com/ 
• Serialize, or structure, your transformed data in a more 
specific way 
• Uses items (one object) and collections (group of objects) 
• Easily embed related resources within each other
Book Transformer 
app/Packback/Transformers/BookTransformer.php 
/** 
* Turn book object into generic array 
* 
* @param Book $book 
* @return array 
*/ 
public function transform(Book $book) 
{ 
return [ 
'id' => (int) $book->id, 
'isbn13' => $book->isbn13, 
'title' => $book->title, 
'author' => $book->author, 
// If we needed to rename the 'price' field to 'msrp' 
'msrp' => '$' . money_format('%i', $book->price) 
]; 
}
User Transformer 
app/Packback/Transformers/UserTransformer.php 
/** 
* Turn user object into generic array 
* 
* @param User $user 
* @return array 
*/ 
public function transform(User $user) 
{ 
return [ 
'id' => (int) $user->id, 
'name' => $user->name, 
'email' => $user->email 
]; 
}
/** 
* List of resources possible to include 
* 
* @var array 
*/ 
protected $availableIncludes = [ 
'books' 
]; 
/** 
* Include books in user 
* 
* @param User $user 
* @return LeagueFractalItemResource 
*/ 
public function includeBooks(User $user) 
{ 
$books = $user->books; 
return $this->collection($books, new BookTransformer); 
}
API Controller 
Extend the ApiController in 
UserController and BookController 
/** 
* Wrapper for Laravel's Response::json() method 
* 
* @param array $array 
* @param array $headers 
* @return mixed 
*/ 
protected function respondWithArray(array $array, array $headers = []) 
{ 
return Response::json($array, $this->statusCode, $headers); 
} 
class UserController extends ApiController 
class BookController extends ApiController 
app/controllers/ApiController.php
/** 
* Respond with Fractal Item 
* 
* @param $item 
* @param $callback 
* @return mixed 
*/ 
protected function respondWithItem($item, $callback) 
{ 
$resource = new Item($item, $callback); 
$rootScope = $this->fractal->createData($resource); 
return $this->respondWithArray($rootScope->toArray()); 
} 
/** 
* Respond with Fractal Collection 
* 
* @param $collection 
* @param $callback 
* @return mixed 
*/ 
protected function respondWithCollection($collection, $callback) 
{ 
$resource = new Collection($collection, $callback); 
$rootScope = $this->fractal->createData($resource); 
return $this->respondWithArray($rootScope->toArray()); 
}
Controller with Fractal 
public function index() 
{ 
$books = Book::all(); 
return $this->respondWithCollection($books, new BookTransformer); 
} 
public function show($book_id) 
{ 
$book = Book::findOrFail($book_id); 
return $this->respondWithItem($book, new BookTransformer); 
}
Improved Postman API Calls 
with Fractal
A Disclaimer 
• This app is an over-simplified example 
• Feel free to ignore everything I’ve told you tonight 
• Different conventions/opinions 
• Strict REST doesn’t make sense for every scenario 
• BUT the more you scale, the harder it will be to keep 
your code organized
REST APIs with Laravel 102 
AKA 
Things we didn’t have time to cover tonight 
• Testing :( 
• Pagination - return lots of records, a little bit at a time 
• Validation 
• Better error handling 
• Authentication 
• OOP best practices + design patterns
Questions? 
Twitter: @samanthageitz 
Email: samanthamichele7@gmail.com

REST APIs in Laravel 101

  • 1.
    REST API Developmentand Testing 101 By Samantha Geitz
  • 2.
    A bit aboutme • 3 years of experience in web development • Now work for Packback • Background mostly in WordPress, a little bit of Rails, now use Laravel full-time • About 1 year experience building APIs
  • 3.
    What we’re goingto talk about tonight • “Typical” Laravel application / MVC • What is an API, and why should you care? • Shitty way to structure an API • Better way to structure an API • Demonstration of interacting with API
  • 4.
    Our Application •Packback • Digital textbook rentals for college students • Resources: Users, Books • Users need to be able to “rent” books • https://github.com/samanthamic hele7/packback-rest-api-101 • Two branches: master and api_with_fractal
  • 5.
    Anatomy of the“Typical” Laravel Application • Model (Eloquent) + database • Controller + routes • View (Blade)
  • 6.
    Problems! • Whathappens if we want to build an iOS/Android application with our data? • What happens if we want to use AngularJS or EmberJS? • What happens when we want to rebuild the front-end in a few years? • What happens if we want to let other companies work with our data? (Twitter API, Facebook API, etc.)
  • 7.
    Solution: Let’s buildan API! (Application Programming Interface)
  • 8.
    What the helldoes that even mean? • APIs only care about data - not what things look like • Data in, data out • JSON is what most of the cool kids are using • Keeps data types intact • You can also use XML if you like typing a lot • Turns everything into a string
  • 9.
    <book> <id>1</id> <title>TheLord of the Rings</title> <author>J. R. R. Tolkien</author> </book> XML Example { "book": { "id" : 1, "title": "The Lord of the Rings", "author": "J. R. R. Tolkien" } } JSON Example
  • 10.
    Laravel makes itreally easy • The client can access routes (which are basically just URLs) • Controllers handle logic (or call other classes to handle it for them) • Get data from or store data in database (via models) • ????? • PROFIT!!! • Return data as JSON
  • 11.
    Stuff that weneed to do • /createUser • /fetchUser • /setUserPassword • /updatePaymentInfo • /addBook • /getBook • /deleteBook • /addBooktoUser • /removeBook
  • 12.
    Okay, this isgetting kind of confusing
  • 13.
    REST to theRescue! • Representational State Transfer • Hopefully not what you’re doing if I’m boring you
  • 14.
    It does CRUD • C - Create (POST) • R - Read (GET) • U - Update (PATCH/PUT) • D - Destroy (DELETE)
  • 15.
    Stuff that weneed to do (the RESTful way) • POST /users - Create a new user • GET /users - Get all users • GET /users/{id} - Get one user by ID • PATCH /users/{id} - Update a user by ID • DELETE /users/{id} - Delete a user by ID • POST /users/{id}/books/{id} - Add a book (or books) to a user • GET /users/{id}/books/ - Get a list of books for a specific user • etc.
  • 16.
    Cool story, bro,but how do I actually build an API?
  • 17.
    Step 1: CreateDatabases • Run database migrations (the same way as in a regular Laravel application) • Books, users, pivot php artisan migrate
  • 18.
    app/database/migrations/2014_11_18_024437_create_users_table.<?php use IlluminateDatabaseSchemaBlueprint; use IlluminateDatabaseMigrationsMigration; class CreateUsersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('users', function(Blueprint $table) { $table->increments('id'); $table->string('email'); $table->string('name'); $table->string('password'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('users'); } }
  • 19.
    app/database/migrations/2014_11_18_024939_create_books_table.<?php use IlluminateDatabaseSchemaBlueprint; use IlluminateDatabaseMigrationsMigration; class CreateBooksTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('books', function(Blueprint $table) { $table->increments('id'); $table->string('isbn13'); $table->string('title'); $table->string('author'); $table->float('price'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('books'); } }
  • 20.
    database/migrations/2014_11_18_025038_create_books_users_<?php use IlluminateDatabaseSchemaBlueprint; use IlluminateDatabaseMigrationsMigration; class CreateBooksUsersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('books_users', function(Blueprint $table) { $table->increments('id'); $table->integer('user_id')->unsigned(); $table->foreign('user_id')->references('id')->on('users'); $table->integer('book_id')->unsigned(); $table->foreign('book_id')->references('id')->on('books'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('books_users', function(Blueprint $table) { $table->dropForeign('books_users_user_id_foreign'); $table->dropForeign('books_users_book_id_foreign'); }); Schema::drop('books_users'); }
  • 21.
    Step 2: Seeddata • Your life will be much easier if you fill your database with fake data • Faker is easy to use and has realistic fake data: https://github.com/fzaninotto/Faker • I generally do one seed file per table • Hook in database/seeds/DatabaseSeeder.php • Make sure you truncate every time the seeder is run or you will end up with a ton of data php artisan db:seed
  • 22.
    Seed Users app/database/seeds/UserTableSeeder.php <?php use CarbonCarbon; use FakerFactory as Faker; class UserTableSeeder extends Seeder { public function run() { $faker = Faker::create(); DB::table('users')->truncate(); for ($i = 0; $i < 50; $i++) { DB::table('users')->insert([ 'email' => $faker->email, 'name' => $faker->name, 'password' => Hash::make($faker->word), 'created_at' => Carbon::now(), 'updated_at' => Carbon::now() ]); } } }
  • 23.
    Seed Books app/database/seeds/BookTableSeeder.php <?php use CarbonCarbon; use FakerFactory as Faker; class BookTableSeeder extends Seeder { public function run() { $faker = Faker::create(); DB::table('books')->truncate(); for ($i = 0; $i < 50; $i++) { DB::table('books')->insert([ 'isbn13' => $faker->ean13(), 'title' => $faker->sentence, 'author' => $faker->name, 'price' => $faker->randomNumber(2) . '.' . $faker->randomNumber(2), 'created_at' => Carbon::now(), 'updated_at' => Carbon::now(), ]); } } }
  • 24.
    Seed User Books app/database/seeds/UserBookTableSeeder.php <?php use CarbonCarbon; class UserBookTableSeeder extends Seeder { public function run() { DB::table('books_users')->truncate(); for ($i = 1; $i < 51; $i++) { DB::table('books_users')->insert([ [ 'user_id' => $i, 'book_id' => $i, 'created_at' => Carbon::now(), 'updated_at' => Carbon::now() ], [ 'user_id' => $i, 'book_id' => 51 - $i, 'created_at' => Carbon::now(), 'updated_at' => Carbon::now() ] ]); } } }
  • 25.
    Step 3: Models • Very little difference compared to a more traditional Laravel app • Define a ManyToMany relationship between users and books
  • 26.
    app/models/User.php class Userextends Eloquent implements UserInterface, RemindableInterface { use UserTrait, RemindableTrait; protected $table = 'users'; protected $fillable = ['email', 'name', 'password']; protected $hidden = array('password', 'remember_token'); public function books() { return $this->belongsToMany('Book', 'books_users'); } public function setPasswordAttribute($password) { $this->attributes['password'] = Hash::make($password); } }
  • 27.
    app/models/Book.php class Bookextends Eloquent { protected $table = 'books'; protected $fillable = ['isbn13', 'title', 'author', 'price']; public function users() { return $this->belongsToMany('User', 'books_users'); } }
  • 28.
    Step 4: Routes • Should you use Laravel magic? (Route::resource() or Route::controller()) • Pros: Less code • Cons: Less code • It is generally clearer (to me) to explicitly define your routes (so you have a blueprint) • However, some people would disagree, so we’ll look at both
  • 29.
    RESTful routes Option1 (Less code) Route::group(['prefix' => 'api'], function() { Route::resource('users', 'UserController'); Route::resource('books', 'BookController'); }); This will automatically look for create, edit, index, show, store, update, and destroy methods in your controller.
  • 30.
    RESTful Routes Option2 (Explicit code) Route::group(['prefix' => 'api'], function() { Route::group(['prefix' => 'books'], function(){ Route::get('', array('uses' => 'BookController@index')); Route::get('{book_id}', array('uses' => 'BookController@show')); Route::post('', array('uses' => 'BookController@store')); Route::patch('{book_id}', array('uses' => 'BookController@update')); Route::delete('{book_id}', array('uses' => 'BookController@destroy')); }); Route::group(['prefix' => 'users'], function(){ Route::get('', array('uses' => 'UserController@index')); Route::get('{user_id}', array('uses' => 'UserController@show')); Route::get('{user_id}/books', array('uses' => 'UserController@showBooks')); Route::post('', array('uses' => 'UserController@store')); Route::post('{user_id}/books/{book_id}', array('uses' => 'UserController@storeBooks')); Route::patch('{user_id}', array('uses' => 'UserController@update')); Route::delete('{user_id}', array('uses' => 'UserController@destroy')); Route::delete('{user_id}/books/{book_id}', array('uses' => 'UserController@destroyBooks')); }); });
  • 31.
    Let’s talk aboutstatus codes • Your API needs to send back a HTTP status code so that the client knows if the succeeded or failed (and if it failed, why) • 2xx - GREAT SUCCESS • 3xx - Redirect somewhere else • 4xx - Client errors • 5xx - Service errors
  • 32.
    Some common statuscodes • 200 - generic OK • 201 - Created OK • 301 - Moved permanently and redirect to new location • 400 - Generic bad request (often used for validation on models) • 401 - Unauthorized (please sign in) • 403 - Unauthorized (you are signed in but shouldn’t be accessing this) • 404 - Does not exist • 500 - API dun goofed • 503 - API is not available for some reason • Plus lots more!
  • 33.
    Step 5: Controllers • You will need (at least) 5 methods: index (get all), show (get one), store, update, destroy • What about create() and edit() (if you use Route::resource())? • You don’t need them if you’re building a pure data-driven API! • Use Postman (http://www.getpostman.com/) to interact with your API instead of Blade templates
  • 34.
    Controllers / Index /** * Get all books * * @return Response */ public function index() { $books = Book::all(); return Response::json([ 'data' => $books ]); } /** * Get all users * * @return Response */ public function index() { $users = User::all(); return Response::json([ 'data' => $users ]); }
  • 35.
    Controllers / Show /** * Get a single user * * @param $user_id * @return Response */ public function show($user_id) { $user = User::findOrFail($user_id); return Response::json([ 'data' => $user ]); } /** * Get a single book * * @param $book_id * @return Response */ public function show($book_id) { $book = Book::findOrFail($book_id); return Response::json([ 'data' => $book ]); }
  • 36.
    Controllers / Store /** * Store a book * * @return Response */ public function store() { $input = Input::only('isbn13', 'title', 'author', 'price'); $book = Book::create($input); return Response::json([ 'data' => $book ]); } /** * Store a user * * @return Response */ public function store() { $input = Input::only('email', 'name', 'password'); $user = User::create($input); return Response::json([ 'data' => $user ]); }
  • 37.
    Controllers / Update /** * Update a book * * @param $book_id * @return Response */ public function update($book_id) { $input = Input::only('isbn13', 'title', 'author', 'price'); $book = Book::find($book_id); $book->update($input); return Response::json([ 'data' => $book ]); } /** * Update a user * * @param $user_id * @return Response */ public function update($user_id) { $input = Input::only('email', 'name', 'password'); $user = User::findOrFail($user_id); $user->update($input); return Response::json([ 'data' => $user ]); }
  • 38.
    Controllers / Destroy /** * Delete a book * * @param $book_id * @return Response */ public function destroy($book_id) { $book = User::findOrFail($book_id); $book->users()->sync([]); $book->delete(); return Response::json([ 'success' => true ]); } /** * Delete a user * * @param $user_id * @return Response */ public function destroy($user_id) { $user = User::findOrFail($user_id); $user->books()->sync([]); $user->delete(); return Response::json([ 'success' => true ]); }
  • 39.
  • 40.
    Let’s Review! •Request is sent through client (we used Postman, but could be AngularJS app, iPhone app, etc.) • Route interprets where it needs to go, sends it to appropriate controller + method • Controller takes the input and figures out what to do with it • Model (Eloquent) interacts with the database • Controller returns the data as JSON • Look, ma, no views!
  • 41.
    A few problems… • We’re relying on the Laravel “hidden” attribute to avoid showing sensitive information but otherwise have no control over what is actually output. This is dangerous. • What happens if our database schema changes? • For example, we need to add a daily vs semester rental price and rename the “price” database column • How can we easily show a user + associated books with one API call?
  • 42.
  • 43.
    Transformers (Not likethe robots) • “Transform” data per resource so that you have a lot more control over what you’re returning and its data type • Easy to build your own, or you can use Fractal for more advanced features: http://fractal.thephpleague.com/ • Serialize, or structure, your transformed data in a more specific way • Uses items (one object) and collections (group of objects) • Easily embed related resources within each other
  • 44.
    Book Transformer app/Packback/Transformers/BookTransformer.php /** * Turn book object into generic array * * @param Book $book * @return array */ public function transform(Book $book) { return [ 'id' => (int) $book->id, 'isbn13' => $book->isbn13, 'title' => $book->title, 'author' => $book->author, // If we needed to rename the 'price' field to 'msrp' 'msrp' => '$' . money_format('%i', $book->price) ]; }
  • 45.
    User Transformer app/Packback/Transformers/UserTransformer.php /** * Turn user object into generic array * * @param User $user * @return array */ public function transform(User $user) { return [ 'id' => (int) $user->id, 'name' => $user->name, 'email' => $user->email ]; }
  • 46.
    /** * Listof resources possible to include * * @var array */ protected $availableIncludes = [ 'books' ]; /** * Include books in user * * @param User $user * @return LeagueFractalItemResource */ public function includeBooks(User $user) { $books = $user->books; return $this->collection($books, new BookTransformer); }
  • 47.
    API Controller Extendthe ApiController in UserController and BookController /** * Wrapper for Laravel's Response::json() method * * @param array $array * @param array $headers * @return mixed */ protected function respondWithArray(array $array, array $headers = []) { return Response::json($array, $this->statusCode, $headers); } class UserController extends ApiController class BookController extends ApiController app/controllers/ApiController.php
  • 48.
    /** * Respondwith Fractal Item * * @param $item * @param $callback * @return mixed */ protected function respondWithItem($item, $callback) { $resource = new Item($item, $callback); $rootScope = $this->fractal->createData($resource); return $this->respondWithArray($rootScope->toArray()); } /** * Respond with Fractal Collection * * @param $collection * @param $callback * @return mixed */ protected function respondWithCollection($collection, $callback) { $resource = new Collection($collection, $callback); $rootScope = $this->fractal->createData($resource); return $this->respondWithArray($rootScope->toArray()); }
  • 49.
    Controller with Fractal public function index() { $books = Book::all(); return $this->respondWithCollection($books, new BookTransformer); } public function show($book_id) { $book = Book::findOrFail($book_id); return $this->respondWithItem($book, new BookTransformer); }
  • 50.
    Improved Postman APICalls with Fractal
  • 51.
    A Disclaimer •This app is an over-simplified example • Feel free to ignore everything I’ve told you tonight • Different conventions/opinions • Strict REST doesn’t make sense for every scenario • BUT the more you scale, the harder it will be to keep your code organized
  • 52.
    REST APIs withLaravel 102 AKA Things we didn’t have time to cover tonight • Testing :( • Pagination - return lots of records, a little bit at a time • Validation • Better error handling • Authentication • OOP best practices + design patterns
  • 53.
    Questions? Twitter: @samanthageitz Email: samanthamichele7@gmail.com