Laravel Montréal #3 - September 25th, 2014
Thanks for comming !
Your feedback is important !
Sponsors
Jobs
Get involved
Comming soon in Laravel Montréal
In computer programming, an application programming interface (API) specifies a software component in terms of its operations, their inputs and outputs and underlying types. Its main purpose is to define a set of functionalities that are independent of their respective implementation, allowing both definition and implementation to vary without compromising each other.
"A RESTful web service (also called a RESTful web API) is a web service implemented using HYYP and the principles of REST. It's a collection of resources..."
- Wikipedia
but there are a series of good practives that you can follow :
Your API will probably still work if you dont follow them...
but it will eventually catch you up !
HTTP/1.1 200 OK
Content-Type: text/html
Not found
HTTP/1.1 200 OK
Content-Type: text/html
"1|My first contact|benjamin.rosell@gmail.com|2014-12-12|2015-12-20|active
\n2|My second contact|jonathan.rosell@gmail.com|2013-10-19|2010-24-04|inactive"
HTTP/1.1 200 OK
Content-Type: text/html
VHlwZSBvciBwYXN0ZSBhbnkgc3RyaW5nIHRvIHRoZSBibHVlIHRleHQgYm94LiBJdCBjYW4gYmUgZWl0aGVyIG5vcm1
hbCBzdHJpbmcgb3IgQkFTRTY0IHN0cmluZy4=
Just follow the RESTful standards
// Simple resource routing
Route::resource('/contacts', 'ContactsController');
Route::group(array('prefix' => 'v1'), function()
{
Route::resource('/contacts', 'ContactsController');
});
php artisan controller:make ContactsController
Route::resource('/contacts', 'ContactsController');
class ContactsController extends BaseController {
public function index() {} // GET /
public function create() {} // GET /create
public function store() {} // POST /
public function show($id) {} // GET /1
public function edit($id) {} // GET /1/edit
public function update($id) {} // PUT /1
public function destroy($id) {} // DELETE /1
}
php artisan routes
php artisan migrate:make create_contacts_table --create="contacts"
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreatePostsTable extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
//
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}
public function up() { Schema::create('contacts', function(Blueprint $table) { $table->increments('id'); $table->string('name', 50); $table->string('email')->nullable()->unique(); $table->string('telephone')->nullable()->unique(); $table->boolean('active')->default(true); $table->timestamps(); }); }
public function down() { Schema::drop('contacts'); }
php artisan migrate
php artisan migrate:rollback
class Contact extends Eloquent {}
/**
* Display a listing of contacts
*
* @return Response
*/
public function index()
{
return Contact::all();
}
/**
* Display a listing of contacts
*
* @return Response
*/
public function index()
{
$contacts = Contact::all();
return Response::json($contacts->toArray());
}
HTTP/1.1 200 OK
Content-Type: text/html
{
[
"id": "1",
"name": "Benjamin Gonzalez",
"email": "benjamin.rosell@gmail.com",
"telephone": "514-236-1889",
"active": "true",
"created_at": "2014-09-25 10:00:00",
"updated_at": "2014-09-25 10:00:00"
]
}
/**
* Store a newly created contact in storage.
*
* @return Response
*/
public function store()
{
$validator = Validator::make($data = Input::all(), Contact::$rules);
if ($validator->fails())
{
App::abort('500', 'The model was not saved, validation failed');
}
$contact = Contact::create($data);
return Response::json($contact->toArray(), 201);
}
REQUEST:
name=Benjamin+Gonzalez&email=benjamin.rosell@gmail.com&telephone=514+236+1889
RESPONSE:
HTTP/1.1 201 Created
Content-Type: text/html
{
"id": "1",
"name": "Benjamin Gonzalez",
"email": "benjamin.rosell@gmail.com",
"telephone": "514-236-1889",
"active": "true",
"created_at": "2014-09-25 10:00:00",
"updated_at": "2014-09-25 10:00:00"
}
/**
* Display the specified contact.
*
* @param int $id
* @return Response
*/
public function show($id)
{
return Response::json(Contact::findOrFail($id));
}
HTTP/1.1 200 OK
Content-Type: text/html
{
"id": "1",
"name": "Benjamin Gonzalez",
"email": "benjamin.rosell@gmail.com",
"telephone": "514-236-1889",
"active": "true",
"created_at": "2014-09-25 10:00:00",
"updated_at": "2014-09-25 10:00:00"
}
/**
* Update the specified resource in storage.
*
* @param int $id
* @return Response
*/
public function update($id)
{
$contact = Contact::findOrFail($id);
$validator = Validator::make($data = Input::all(), Contact::$rules);
if ($validator->fails())
{
App::abort('500', 'The model was not saved');
}
return Response::json($contact->update($data));
}
REQUEST:
name=Benjamin+Rosell
RESPONSE:
HTTP/1.1 200 Ok
Content-Type: text/html
{
"id": "1",
"name": "Benjamin Rosell",
"email": "benjamin.rosell@gmail.com",
"telephone": "514-236-1889",
"active": "true",
"created_at": "2014-09-25 10:00:00",
"updated_at": "2014-09-25 10:00:00"
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return Response
*/
public function destroy($id)
{
Contact::destroy($id);
return Response::make(null, 204);
}
}
HTTP/1.1 204 No content
200: The request was successful.
201: The resource was successfully created.
204: The request was successful, but we did not send any content back.
400: The request failed due to an application error, such as a validation error.
401: An API key was either not sent or invalid.
403: The resource does not belong to the authenticated user and is forbidden.
404: The resource was not found.
500: A server error occurred.
// General HttpException handler
App::error(function(Symfony\Component\HttpKernel\Exception\HttpException $e, $code)
{
$headers = $e->getHeaders();
switch ($code)
{
case 401:
$default_message = 'Invalid API key';
$headers['WWW-Authenticate'] = 'Basic realm="REST API"';
break;
case 403:
$default_message = 'Insufficient privileges to perform this action';
break;
case 404:
$default_message = 'The requested resource was not found';
break;
default:
$default_message = 'An error was encountered';
}
return Response::json(array(
'error' => $e->getMessage() ?: $default_message,
), $code, $headers);
});
App::error(function(ErrorMessageException $e)
{
$messages = $e->getMessages()->all();
return Response::json(array(
'error' => $messages[0],
), 400);
});
// NotFoundException handler
App::error(function(NotFoundException $e)
{
$default_message = 'The requested resource was not found';
return Response::json(array(
'error' => $e->getMessage() ?: $default_message,
), 404);
});
// PermissionException handler
App::error(function(PermissionException $e)
{
$default_message = 'Insufficient privileges to perform this action';
return Response::json(array(
'error' => $e->getMessage() ?: $default_message,
), 403);
});
class ErrorMessageException extends RuntimeException {}
class NotFoundException extends RuntimeException {}
class PermissionException extends RuntimeException {}
HTTP/1.1 404 Not found
Content-Type: application/json
{
"error": "The requested resource was not found";
}
First we neet to create an API key...
/**
* Generate a random, unique API key.
*
* @return string
*/
public static function createApiKey()
{
return Str::random(32);
}
User::creating(function($user)
{
$user->api_key = User::createApiKey();
});
Route::filter('api.auth', function()
{
if (!Request::getUser())
{
App::abort(401, 'A valid API key is required');
}
$user = User::where('api_key', '=', Request::getUser())->first();
if (!$user)
{
App::abort(401);
}
Auth::login($user);
});
Route::filter('api.limit', function()
{
$key = sprintf('api:%s', Auth::user()->api_key);
// Create the key if it doesn't exist
Cache::add($key, 0, 60);
// Increment by 1
$count = Cache::increment($key);
// Fail if hourly requests exceeded
if ($count > Config::get('api.requests_per_hour'))
{
App::abort(403, 'Hourly request limit exceeded');
}
});