rest apis in laravel 101

53
REST API Development and Testing 101 By Samantha Geitz

Upload: samantha-geitz

Post on 12-Jul-2015

2.498 views

Category:

Technology


2 download

TRANSCRIPT

Page 1: REST APIs in Laravel 101

REST API

Development and

Testing 101

By Samantha Geitz

Page 2: REST APIs in Laravel 101

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

Page 3: REST APIs in Laravel 101

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

Page 4: REST APIs in Laravel 101

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

Page 5: REST APIs in Laravel 101

Anatomy of the“Typical”

Laravel Application

• Model (Eloquent) + database

• Controller + routes

• View (Blade)

Page 6: REST APIs in Laravel 101

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.)

Page 7: REST APIs in Laravel 101

Solution:

Let’s build an API!

(Application Programming Interface)

Page 8: REST APIs in Laravel 101

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

Page 9: REST APIs in Laravel 101

<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

Page 10: REST APIs in Laravel 101

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

Page 11: REST APIs in Laravel 101

Stuff that we need to do

• /createUser

• /fetchUser

• /setUserPassword

• /updatePaymentInfo

• /addBook

• /getBook

• /deleteBook

• /addBooktoUser

• /removeBook

Page 12: REST APIs in Laravel 101

Okay, this is getting

kind of confusing

Page 13: REST APIs in Laravel 101

REST to the Rescue!

• Representational State Transfer

• Hopefully not what you’re doing if I’m boring you

Page 14: REST APIs in Laravel 101

It does CRUD

• C - Create (POST)

• R - Read (GET)

• U - Update (PATCH/PUT)

• D - Destroy (DELETE)

Page 15: REST APIs in Laravel 101

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.

Page 16: REST APIs in Laravel 101

Cool story, bro, but how

do I actually build an API?

Page 17: REST APIs in Laravel 101

Step 1: Create Databases

• Run database migrations (the same way as in a regular

Laravel application)

• Books, users, pivot

php artisan migrate

Page 18: REST APIs in Laravel 101

<?php

use Illuminate\Database\Schema\Blueprint;

use Illuminate\Database\Migrations\Migration;

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_024437_create_users_table.php

Page 19: REST APIs in Laravel 101

app/database/migrations/2014_11_18_024939_create_books_table.php

<?php

use Illuminate\Database\Schema\Blueprint;

use Illuminate\Database\Migrations\Migration;

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

}

}

Page 20: REST APIs in Laravel 101

app/database/migrations/2014_11_18_025038_create_books_users_table.php

<?php

use Illuminate\Database\Schema\Blueprint;

use Illuminate\Database\Migrations\Migration;

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

}

}

Page 21: REST APIs in Laravel 101

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

Page 22: REST APIs in Laravel 101

Seed Users

<?php

use Carbon\Carbon;

use Faker\Factory 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()

]);

}

}

}

app/database/seeds/UserTableSeeder.php

Page 23: REST APIs in Laravel 101

Seed Booksapp/database/seeds/BookTableSeeder.php

<?php

use Carbon\Carbon;

use Faker\Factory 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(),

]);

}

}

}

Page 24: REST APIs in Laravel 101

Seed User Booksapp/database/seeds/UserBookTableSeeder.php

<?php

use Carbon\Carbon;

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()

]

]);

}

}

}

Page 25: REST APIs in Laravel 101

Step 3: Models

• Very little difference compared to a more traditional

Laravel app

• Define a ManyToMany relationship between users and

books

Page 26: REST APIs in Laravel 101

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

}

}

Page 27: REST APIs in Laravel 101

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

}

}

Page 28: REST APIs in Laravel 101

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

Page 29: REST APIs in Laravel 101

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.

Page 30: REST APIs in Laravel 101

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

});

});

Page 31: REST APIs in Laravel 101

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

Page 32: REST APIs in Laravel 101

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!

Page 33: REST APIs in Laravel 101

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

Page 34: REST APIs in Laravel 101

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

]);

}

Page 35: REST APIs in Laravel 101

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

]);

}

Page 36: REST APIs in Laravel 101

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

]);

}

Page 37: REST APIs in Laravel 101

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

]);

}

Page 38: REST APIs in Laravel 101

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

]);

}

Page 39: REST APIs in Laravel 101

Postman

Demonstration

Page 40: REST APIs in Laravel 101

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!

Page 41: REST APIs in Laravel 101

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?

Page 42: REST APIs in Laravel 101

Use transformers!

Page 43: REST APIs in Laravel 101

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

Page 44: REST APIs in Laravel 101

Book Transformer

/**

* 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)

];

}

app/Packback/Transformers/BookTransformer.php

Page 45: REST APIs in Laravel 101

User Transformer

/**

* 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

];

}

app/Packback/Transformers/UserTransformer.php

Page 46: REST APIs in Laravel 101

/**

* List of resources possible to include

*

* @var array

*/

protected $availableIncludes = [

'books'

];

/**

* Include books in user

*

* @param User $user

* @return League\Fractal\ItemResource

*/

public function includeBooks(User $user)

{

$books = $user->books;

return $this->collection($books, new BookTransformer);

}

Page 47: REST APIs in Laravel 101

API Controller

/**

* 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

Extend the ApiController in

UserController and BookController

class BookController extends ApiController

app/controllers/ApiController.php

Page 48: REST APIs in Laravel 101

/**

* 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());

}

Page 49: REST APIs in Laravel 101

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

}

Page 50: REST APIs in Laravel 101

Improved Postman API Calls

with Fractal

Page 51: REST APIs in Laravel 101

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

Page 52: REST APIs in Laravel 101

REST APIs with Laravel 102AKA

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

Page 53: REST APIs in Laravel 101

Questions?

Twitter: @samanthageitz

Email: [email protected]