Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

REST APIs in Laravel 101

6,501 views

Published on

Slides from a presentation given at Laravel Chicago on November 18, 2014. Goes over the basics of building a REST API using the Laravel framework as well as some handy tips and tools.

Published in: Technology
  • Thanks you very much. Its very helpful for beginners.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

REST APIs in Laravel 101

  1. 1. REST API Development and Testing 101 By Samantha Geitz
  2. 2. 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
  3. 3. 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
  4. 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. 5. Anatomy of the“Typical” Laravel Application • Model (Eloquent) + database • Controller + routes • View (Blade)
  6. 6. 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.)
  7. 7. Solution: Let’s build an API! (Application Programming Interface)
  8. 8. 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
  9. 9. <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
  10. 10. 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
  11. 11. Stuff that we need to do • /createUser • /fetchUser • /setUserPassword • /updatePaymentInfo • /addBook • /getBook • /deleteBook • /addBooktoUser • /removeBook
  12. 12. Okay, this is getting kind of confusing
  13. 13. REST to the Rescue! • Representational State Transfer • Hopefully not what you’re doing if I’m boring you
  14. 14. It does CRUD • C - Create (POST) • R - Read (GET) • U - Update (PATCH/PUT) • D - Destroy (DELETE)
  15. 15. 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.
  16. 16. Cool story, bro, but how do I actually build an API?
  17. 17. Step 1: Create Databases • Run database migrations (the same way as in a regular Laravel application) • Books, users, pivot php artisan migrate
  18. 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. 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. 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. 21. 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
  22. 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. 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. 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. 25. Step 3: Models • Very little difference compared to a more traditional Laravel app • Define a ManyToMany relationship between users and books
  26. 26. 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); } }
  27. 27. 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'); } }
  28. 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. 29. 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.
  30. 30. 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')); }); });
  31. 31. 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
  32. 32. 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!
  33. 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. 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. 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. 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. 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. 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. 39. Postman Demonstration
  40. 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. 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. 42. Use transformers!
  43. 43. 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
  44. 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. 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. 46. /** * 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); }
  47. 47. 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
  48. 48. /** * 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()); }
  49. 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. 50. Improved Postman API Calls with Fractal
  51. 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. 52. 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
  53. 53. Questions? Twitter: @samanthageitz Email: samanthamichele7@gmail.com

×