SlideShare una empresa de Scribd logo
1 de 53
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

Más contenido relacionado

La actualidad más candente

Learn REST API with Python
Learn REST API with PythonLearn REST API with Python
Learn REST API with Python
Larry Cai
 

La actualidad más candente (20)

Angular Dependency Injection
Angular Dependency InjectionAngular Dependency Injection
Angular Dependency Injection
 
2 years with python and serverless
2 years with python and serverless2 years with python and serverless
2 years with python and serverless
 
Whitebox testing of Spring Boot applications
Whitebox testing of Spring Boot applicationsWhitebox testing of Spring Boot applications
Whitebox testing of Spring Boot applications
 
Laravel Eloquent ORM
Laravel Eloquent ORMLaravel Eloquent ORM
Laravel Eloquent ORM
 
Optional in Java 8
Optional in Java 8Optional in Java 8
Optional in Java 8
 
Learn REST API with Python
Learn REST API with PythonLearn REST API with Python
Learn REST API with Python
 
Spring Framework - MVC
Spring Framework - MVCSpring Framework - MVC
Spring Framework - MVC
 
Learn flask in 90mins
Learn flask in 90minsLearn flask in 90mins
Learn flask in 90mins
 
Play with FILE Structure - Yet Another Binary Exploit Technique
Play with FILE Structure - Yet Another Binary Exploit TechniquePlay with FILE Structure - Yet Another Binary Exploit Technique
Play with FILE Structure - Yet Another Binary Exploit Technique
 
Coroutines in Kotlin
Coroutines in KotlinCoroutines in Kotlin
Coroutines in Kotlin
 
Jose portillo dev con presentation 1138
Jose portillo   dev con presentation 1138Jose portillo   dev con presentation 1138
Jose portillo dev con presentation 1138
 
Devoxx : being productive with JHipster
Devoxx : being productive with JHipsterDevoxx : being productive with JHipster
Devoxx : being productive with JHipster
 
Basic auth implementation using raml in mule
Basic auth implementation using raml in muleBasic auth implementation using raml in mule
Basic auth implementation using raml in mule
 
Ansible for beginners
Ansible for beginnersAnsible for beginners
Ansible for beginners
 
Deciphering Explain Output
Deciphering Explain Output Deciphering Explain Output
Deciphering Explain Output
 
Pwning in c++ (basic)
Pwning in c++ (basic)Pwning in c++ (basic)
Pwning in c++ (basic)
 
Class 3 - PHP Functions
Class 3 - PHP FunctionsClass 3 - PHP Functions
Class 3 - PHP Functions
 
Redis
RedisRedis
Redis
 
REST APIs with Spring
REST APIs with SpringREST APIs with Spring
REST APIs with Spring
 
Automating with Ansible
Automating with AnsibleAutomating with Ansible
Automating with Ansible
 

Destacado

Laravel Restful API and AngularJS
Laravel Restful API and AngularJSLaravel Restful API and AngularJS
Laravel Restful API and AngularJS
Blake Newman
 
software testing on whatsapp
software testing on whatsappsoftware testing on whatsapp
software testing on whatsapp
nil65
 

Destacado (20)

Laravel 5 Annotations: RESTful API routing
Laravel 5 Annotations: RESTful API routingLaravel 5 Annotations: RESTful API routing
Laravel 5 Annotations: RESTful API routing
 
Web services with laravel
Web services with laravelWeb services with laravel
Web services with laravel
 
RESTful API development in Laravel 4 - Christopher Pecoraro
RESTful API development in Laravel 4 - Christopher PecoraroRESTful API development in Laravel 4 - Christopher Pecoraro
RESTful API development in Laravel 4 - Christopher Pecoraro
 
Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5
 
REST API Laravel
REST API LaravelREST API Laravel
REST API Laravel
 
Your rest api using laravel
Your rest api using laravelYour rest api using laravel
Your rest api using laravel
 
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
 
Laravel 5.2 Gates, AuthServiceProvider and Policies
Laravel 5.2 Gates, AuthServiceProvider and PoliciesLaravel 5.2 Gates, AuthServiceProvider and Policies
Laravel 5.2 Gates, AuthServiceProvider and Policies
 
Javascript laravel's friend
Javascript laravel's friendJavascript laravel's friend
Javascript laravel's friend
 
Super powered API testing
Super powered API testing Super powered API testing
Super powered API testing
 
jQuery Keynote - Fall 2010
jQuery Keynote - Fall 2010jQuery Keynote - Fall 2010
jQuery Keynote - Fall 2010
 
Service-Oriented Architecture
Service-Oriented ArchitectureService-Oriented Architecture
Service-Oriented Architecture
 
Developing and Testing a MongoDB and Node.js REST API
Developing and Testing a MongoDB and Node.js REST APIDeveloping and Testing a MongoDB and Node.js REST API
Developing and Testing a MongoDB and Node.js REST API
 
Laravel Restful API and AngularJS
Laravel Restful API and AngularJSLaravel Restful API and AngularJS
Laravel Restful API and AngularJS
 
SOAP-UI The Web service Testing
SOAP-UI The Web service TestingSOAP-UI The Web service Testing
SOAP-UI The Web service Testing
 
Api testing
Api testingApi testing
Api testing
 
4 Major Advantages of API Testing
4 Major Advantages of API Testing4 Major Advantages of API Testing
4 Major Advantages of API Testing
 
OpenStack Tempest and REST API testing
OpenStack Tempest and REST API testingOpenStack Tempest and REST API testing
OpenStack Tempest and REST API testing
 
Auto encoding-variational-bayes
Auto encoding-variational-bayesAuto encoding-variational-bayes
Auto encoding-variational-bayes
 
software testing on whatsapp
software testing on whatsappsoftware testing on whatsapp
software testing on whatsapp
 

Similar a REST APIs in Laravel 101

wwc start-launched
wwc start-launchedwwc start-launched
wwc start-launched
Mat Schaffer
 
Learning to code for startup mvp session 3
Learning to code for startup mvp session 3Learning to code for startup mvp session 3
Learning to code for startup mvp session 3
Henry S
 
PTW Rails Bootcamp
PTW Rails BootcampPTW Rails Bootcamp
PTW Rails Bootcamp
Mat Schaffer
 
WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...
Fabio Franzini
 
Rapid API Development ArangoDB Foxx
Rapid API Development ArangoDB FoxxRapid API Development ArangoDB Foxx
Rapid API Development ArangoDB Foxx
Michael Hackstein
 
Jooctrine - Doctrine ORM in Joomla!
Jooctrine - Doctrine ORM in Joomla!Jooctrine - Doctrine ORM in Joomla!
Jooctrine - Doctrine ORM in Joomla!
Herman Peeren
 

Similar a REST APIs in Laravel 101 (20)

Supa fast Ruby + Rails
Supa fast Ruby + RailsSupa fast Ruby + Rails
Supa fast Ruby + Rails
 
Getting to know Laravel 5
Getting to know Laravel 5Getting to know Laravel 5
Getting to know Laravel 5
 
MVS: An angular MVC
MVS: An angular MVCMVS: An angular MVC
MVS: An angular MVC
 
Laravel for Web Artisans
Laravel for Web ArtisansLaravel for Web Artisans
Laravel for Web Artisans
 
Getting Started with Rails
Getting Started with RailsGetting Started with Rails
Getting Started with Rails
 
Rails 4.0
Rails 4.0Rails 4.0
Rails 4.0
 
Riding the Edge with Ember.js
Riding the Edge with Ember.jsRiding the Edge with Ember.js
Riding the Edge with Ember.js
 
Wider than rails
Wider than railsWider than rails
Wider than rails
 
wwc start-launched
wwc start-launchedwwc start-launched
wwc start-launched
 
Learning to code for startup mvp session 3
Learning to code for startup mvp session 3Learning to code for startup mvp session 3
Learning to code for startup mvp session 3
 
Staying Sane with Drupal NEPHP
Staying Sane with Drupal NEPHPStaying Sane with Drupal NEPHP
Staying Sane with Drupal NEPHP
 
SproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsSproutCore and the Future of Web Apps
SproutCore and the Future of Web Apps
 
cake phptutorial
cake phptutorialcake phptutorial
cake phptutorial
 
Rails and the Apache SOLR Search Engine
Rails and the Apache SOLR Search EngineRails and the Apache SOLR Search Engine
Rails and the Apache SOLR Search Engine
 
PTW Rails Bootcamp
PTW Rails BootcampPTW Rails Bootcamp
PTW Rails Bootcamp
 
WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...
 
Rapid API Development ArangoDB Foxx
Rapid API Development ArangoDB FoxxRapid API Development ArangoDB Foxx
Rapid API Development ArangoDB Foxx
 
Jooctrine - Doctrine ORM in Joomla!
Jooctrine - Doctrine ORM in Joomla!Jooctrine - Doctrine ORM in Joomla!
Jooctrine - Doctrine ORM in Joomla!
 
Introduction to Laravel Framework (5.2)
Introduction to Laravel Framework (5.2)Introduction to Laravel Framework (5.2)
Introduction to Laravel Framework (5.2)
 
Connecting the Worlds of Java and Ruby with JRuby
Connecting the Worlds of Java and Ruby with JRubyConnecting the Worlds of Java and Ruby with JRuby
Connecting the Worlds of Java and Ruby with JRuby
 

Último

Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Victor Rentea
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Victor Rentea
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
WSO2
 

Último (20)

AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
WSO2's API Vision: Unifying Control, Empowering Developers
WSO2's API Vision: Unifying Control, Empowering DevelopersWSO2's API Vision: Unifying Control, Empowering Developers
WSO2's API Vision: Unifying Control, Empowering Developers
 
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdfRising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 AmsterdamDEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
 
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
 
Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
 
Vector Search -An Introduction in Oracle Database 23ai.pptx
Vector Search -An Introduction in Oracle Database 23ai.pptxVector Search -An Introduction in Oracle Database 23ai.pptx
Vector Search -An Introduction in Oracle Database 23ai.pptx
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
 
CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistan
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
 
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
 
MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 

REST APIs in Laravel 101

  • 1. REST API Development and Testing 101 By Samantha Geitz
  • 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. 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. 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! • 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. Solution: Let’s build an API! (Application Programming Interface)
  • 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. <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. 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. Stuff that we need to do • /createUser • /fetchUser • /setUserPassword • /updatePaymentInfo • /addBook • /getBook • /deleteBook • /addBooktoUser • /removeBook
  • 12. Okay, this is getting kind of confusing
  • 13. REST to the Rescue! • 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 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. Cool story, bro, but how do I actually build an API?
  • 17. Step 1: Create Databases • 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: 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. 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 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. 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. 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 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. 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. 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. 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. 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 ]); }
  • 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?
  • 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. 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. /** * 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. 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. /** * 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. 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 API Calls 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 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. Questions? Twitter: @samanthageitz Email: samanthamichele7@gmail.com