writing apis in lumen - akrabat.com · content negotiation. correctly parse the request • read...

76
Writing APIs in Lumen Rob Allen June 2018

Upload: others

Post on 20-May-2020

12 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Writing APIs in LumenRob Allen

  

June 2018

Page 2: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

I write APIs

Rob Allen ~ @akrabat

Page 3: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Let's start with Lumen

Rob Allen ~ @akrabat

Page 4: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Lumen• Microframework• Built with Laravel components• Fast!

Rob Allen ~ @akrabat

Page 5: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

InstallationInstall Laravel Installer:

$ composer global require laravel/lumen-installer

Create a new project:$ lumen new my-project-name

Create .env file$ cd my-project-name$ cp .env.example .env

Rob Allen ~ @akrabat

Page 6: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Run using local webserver$ php -S 127.0.0.1:8888 -t public/

$ curl http://localhost:8888Lumen (5.6.3) (Laravel Components 5.6.*)

Rob Allen ~ @akrabat

Page 7: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Differences from Laravel: ArtisanArtisan is pared down:

• No serve • ONLY make:migration• No key:generate • No make:model• No tinker • No make:controller• No env • No make:auth• No down • etc

Rob Allen ~ @akrabat

Page 8: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Add more Artisan!1. Install flipbox's Lumen Generator:

$ composer require --dev flipbox/lumen-generator

2. Inside bootstrap/app.php file, add:$app->register(Flipbox\LumenGenerator\ LumenGeneratorServiceProvider::class);

Rob Allen ~ @akrabat

Page 9: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Lumen-GeneratorProvides these additional artisan commands:make:command make:controller key:generatemake:event make:job clear-compiledmake:listener make:mail servemake:middleware make:migration tinkermake:model make:policy optimizemake:provider make:seeder route:listmake:test

Rob Allen ~ @akrabat

Page 10: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Differences from Laravel: Config• No config/ directory• Configuration only via .env

APP_ENV=localAPP_DEBUG=trueAPP_KEY=APP_TIMEZONE=UTC

LOG_CHANNEL=stackLOG_SLACK_WEBHOOK_URL=…

Rob Allen ~ @akrabat

Page 11: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Differences from Laravel: app.php• Register service providers in bootstrap/app.php

$app->register(App\Providers\AppServiceProvider::class);

• Register middleware in bootstrap/app.php$app->middleware([ App\Http\Middleware\ExampleMiddleware::class]);

$app->routeMiddleware([ 'auth' => App\Http\Middleware\Authenticate::class,]);

Rob Allen ~ @akrabat

Page 12: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Differences from Laravel: RoutingLaravel (Symfony Router):Route::get("/books/{id?}", function($id = null) { // do stuff})->where('id', '[0-9]+');

Lumen (FastRoute):$router->get('/books[/{id:[0-9]+}]', function ($id = null) { // do stuff});

Rob Allen ~ @akrabat

Page 13: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Disabled featuresbootstrap/app.php:

// $app->withFacades();

// $app->withEloquent();

Rob Allen ~ @akrabat

Page 14: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

/ping: An API's "Hello World"routes/web.php:

$router->get('/ping', function () { return response()->json(['ack' => time()]);});

Rob Allen ~ @akrabat

Page 15: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

/ping: An API's "Hello World"routes/web.php:

$router->get('/ping', function () { return response()->json(['ack' => time()]);});

$ curl -i http://localhost:8888/pingHTTP/1.0 200 OKHost: localhost:8888Content-Type: application/json

{"ack":1527326698}

Rob Allen ~ @akrabat

Page 16: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

What is an API?

Rob Allen ~ @akrabat

Page 17: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

What is a web API?A server-side web API is a programmatic interfaceconsisting of one or more publicly exposed endpointsto a defined request–response message system,typically expressed in JSON or XML, which is exposedvia the web

Wikipedia

Rob Allen ~ @akrabat

Page 18: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

What is a web API?• Programmatic interface• Endpoints• Request-response message system• JSON or XML• Stateless

Rob Allen ~ @akrabat

Page 19: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

What is REST?• An architecture• Centres on the transfer of representations of resources

• A resource is any concept that can be addressed• A representation is typically a document that captures the

current or intended state of a resource• A client makes requests of a server when it wants to transition

to a new state

Rob Allen ~ @akrabat

Page 20: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Strengths• Loose coupling• Leverages the power of HTTP• Emphasis on readability

• HTTP methods as verbs: GET, POST, PUT, DELETE, etc.• Resources as nouns: collections and entities

Rob Allen ~ @akrabat

Page 21: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Constraints: Client/Server• Clients are not concerned with storage (portable)• Servers are not concerned with UI or user state (scalable)

Rob Allen ~ @akrabat

Page 22: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Constraints: Stateless• No client context stored between requests. (no sessions!)

Rob Allen ~ @akrabat

Page 23: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Constraints: Cacheable• Safe idempotent methods are always cacheable• Non-idempotent methods should allow clients to cache

responses.• Clients should honour HTTP headers with respect to caching.

Rob Allen ~ @akrabat

Page 24: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Constraints: Layered system• Client should not care whether it is connected directly to the

server, or to an intermediary proxy.

Rob Allen ~ @akrabat

Page 25: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Constraints: Uniform Interface• Identification of resources• Manipulation of resources through representations• Self-descriptive messages• Hypermedia as the engine of application state (HATEOAS)

Rob Allen ~ @akrabat

Page 26: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Primary aspects of a RESTful API• URI for each resource: https://example.com/authors/1• HTTP methods are the set of operations allowed for the

resource• Media type used for representations of the resource• The API must be hypertext driven

Rob Allen ~ @akrabat

Page 27: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

HTTP methodsMethod Used for Idempotent?GET Retrieve data YesPUT Change data YesDELETE Delete data YesPOST Change data NoPATCH Update data No

Send 405 Method Not Allowed not available for that resource

Rob Allen ~ @akrabat

Page 28: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

HTTP method negotiationLumen provides this for free!

$ curl -I -X DELETE http://localhost:8888

HTTP/1.0 405 Method Not AllowedHost: localhost:8888Allow: GET, POSTConnection: closeContent-type: text/html; charset=UTF-8

Rob Allen ~ @akrabat

Page 29: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Routing in Laravelroutes/web.php:

$router->get('/authors/{id:\d+}', [ 'as' => 'author.list', 'uses' => 'AuthorController@show']);

Rob Allen ~ @akrabat

Page 30: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Routes have a methodSingle methods:

$router->get() $router->post() $router->put() $router->patch() $router->delete() $router->options()

Multiple methods: $router->addRoute(['GET', 'POST'], …)

Rob Allen ~ @akrabat

Page 31: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Routes have a pattern• Literal string match

$router->get('/authors', …);

Rob Allen ~ @akrabat

Page 32: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Routes have a pattern• Literal string match

$router->get('/authors', …);• Placeholders are wrapped in { }

$router->get('/authors/{id}', …);

Rob Allen ~ @akrabat

Page 33: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Routes have a pattern• Literal string match

$router->get('/authors', …);• Placeholders are wrapped in { }

$router->get('/authors/{id}', …);• Optional segments are wrapped with [ ]

$router->get('/authors[/{id}[/{books}]]', …);

Rob Allen ~ @akrabat

Page 34: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Routes have a pattern• Literal string match

$router->get('/authors', …);• Placeholders are wrapped in { }

$router->get('/authors/{id}', …);• Optional segments are wrapped with [ ]

$router->get('/authors[/{id}[/{books}]]', …);• Constrain placeholders via Regex

$router->get('/authors/{id:\d+}', …); // digits

Rob Allen ~ @akrabat

Page 35: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Routes have a nameUse as key to specify a name:$router->get('/authors/{id:\d+}', [ 'as' => 'author.list', 'uses' => 'AuthorController@show']);

Generate URL to named route:$url = route('authors', ['id' => 1]);

// generates: /authors/1

Rob Allen ~ @akrabat

Page 36: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Routes have an actionUse uses key to specify a controller:$router->get('/authors/{id:\d+}', [ 'as' => 'author.list', 'uses' => 'AuthorController@show']);

Rob Allen ~ @akrabat

Page 37: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Action method in a controllernamespace App\Http\Controllers;

use …;

class AuthorController extends Controller{ public function show(int $id) { $author = Author::findOrFail($id); return $author; }}

Rob Allen ~ @akrabat

Page 38: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Status codesSend the right one for the right situation!1xx Informational2xx Success3xx Redirection4xx Client error5xx Server error

Rob Allen ~ @akrabat

1xx: Request received, continuing process.
2xx: All done.
3xx: Client needs to do something to complete the request.
4xx: Your fault.
5xx: Our fault.
Page 39: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Status codes are set in the Response// AuthorControllerpublic function add(Request $request): Response{ $data = $this->validate($request, [ 'name' => 'required|max:100', ]);

$author = Author::create($data)->save(); return response()->json($author, 201);}

Rob Allen ~ @akrabat

Page 40: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Status codes$ curl -i -d name="Octavia Butler" \ http://localhost:8888/authors

HTTP/1.0 201 CreatedHost: localhost:8888Content-Type: application/json

{"name":"Octavia Butler","updated_at":"2018-05-26 14:55:27","created_at":"2018-05-26 14:55:27","id":7}

Rob Allen ~ @akrabat

Page 41: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Content negotiationCorrectly parse the request

• Read the Content-Type header• Raise "415 Unsupported media type" status if

unsupportedCorrectly create the response

• Read the Accept header• Set the Content-Type header

Rob Allen ~ @akrabat

Lumen provides form-url-encoded and JSON parsing of the request for free
We just need to worry about raising the errors
Page 42: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Handling unsupported typesclass UnsupportedMiddleware{ public function handle($request, Closure $next) { $type = $request->headers->get('content-type'); if (stripos($type, 'application/json') !== 0) { return response('Unsupported Media Type', 415); } return $next($request); }}

Rob Allen ~ @akrabat

Page 43: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Handling unsupported typesclass UnsupportedMiddleware{ public function handle($request, Closure $next) { $type = $request->headers->get('content-type'); if (stripos($type, 'application/json') !== 0) { return response('Unsupported Media Type', 415); } return $next($request); }}

Rob Allen ~ @akrabat

Page 44: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Handling unsupported typesclass UnsupportedMiddleware{ public function handle($request, Closure $next) { $type = $request->headers->get('content-type'); if (stripos($type, 'application/json') !== 0) { return response('Unsupported Media Type', 415); } return $next($request); }}

Rob Allen ~ @akrabat

Page 45: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Handling invalid Accept headerclass UnacceptableMiddleware{ public function handle($request, Closure $next) { $accept = $request->headers->get('accept'); if ($accept && stripos($accept, 'json') === false) { return response->json(['error' => 'You must accept JSON'], 406); } return $next($request); }}

Rob Allen ~ @akrabat

Page 46: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Handling invalid Accept headerclass UnacceptableMiddleware{ public function handle($request, Closure $next) { $accept = $request->headers->get('accept'); if ($accept && stripos($accept, 'json') === false) { return response->json(['error' => 'You must accept JSON'], 406); } return $next($request); }}

Rob Allen ~ @akrabat

Page 47: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Handling invalid Accept headerclass UnacceptableMiddleware{ public function handle($request, Closure $next) { $accept = $request->headers->get('accept'); if ($accept && stripos($accept, 'json') === false) { return response->json(['error' => 'You must accept JSON'], 406); } return $next($request); }}

Rob Allen ~ @akrabat

Page 48: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Handling invalid Accept header$ curl -i -H "Accept: application/xml" http://localhost/HTTP/1.0 406 Not AcceptableContent-Type: application/json

{"error":"You must accept JSON"}

Rob Allen ~ @akrabat

Page 49: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Hypermedia• Media type used for a representation• The link relations between representations and/or states• Important for discoverability

Rob Allen ~ @akrabat

Page 50: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

JSON and HypermediaJSON does not have a defined way of providing hypermedia links

Options:• "Link" header (GitHub approach)• application/collection+json• application/hal+json• JSON-API

Rob Allen ~ @akrabat

Page 51: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Fractal• Separate the logic for your JSON formation from your

Eloquent model• Supports multiple serialisers including JSON-API• Install:

$ composer require league/fractal

Rob Allen ~ @akrabat

Page 52: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Fractal service providerclass FractalManagerProvider extends ServiceProvider{ public function register() { $this->app->singleton(Manager::class,function($app) { $manager = new Manager(); $base = app(Request::class)->getBaseURL(); $manager->setSerializer(new JsonApiSerializer($base)); return $manager; }); }}

Rob Allen ~ @akrabat

Register in bootstrap/app.php
There's quite a lot going on here, so let's look at each bit in turn
Page 53: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Fractal service providerclass FractalManagerProvider extends ServiceProvider{ public function register() { $this->app->singleton(Manager::class,function($app) { $manager = new Manager(); $base = app(Request::class)->getBaseURL(); $manager->setSerializer(new JsonApiSerializer($base)); return $manager; }); }}

Rob Allen ~ @akrabat

Page 54: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Fractal service providerclass FractalManagerProvider extends ServiceProvider{ public function register() { $this->app->singleton(Manager::class,function($app) { $manager = new Manager(); $base = app(Request::class)->getBaseURL(); $manager->setSerializer(new JsonApiSerializer($base)); return $manager; }); }}

Rob Allen ~ @akrabat

Page 55: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Fractal service providerclass FractalManagerProvider extends ServiceProvider{ public function register() { $this->app->singleton(Manager::class,function($app) { $manager = new Manager(); $base = app(Request::class)->getBaseURL(); $manager->setSerializer(new JsonApiSerializer($base)); return $manager; }); }}

Rob Allen ~ @akrabat

Page 56: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

AuthorTransformerclass AuthorTransformer extends Fractal\TransformerAbstract{ public function transform(Author $author) { return [ 'id' => (int) $author->id, 'name' => $author->name, ]; }}

Rob Allen ~ @akrabat

Page 57: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Create JSON-API responsepublic function list(Manager $fractal) : Response{ $authors = Author::all();

$resource = new Collection($authors, new AuthorTransformer, 'authors'); return response()->json( $fractal->createData($resource)->toArray(), 200, ['content-type' => 'application/vnd.api+json'] );}

Rob Allen ~ @akrabat

There's quite a lot going on here, so let's look at each bit in turn
Page 58: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Create JSON-API responsepublic function list(Manager $fractal) : Response{ $authors = Author::all();

$resource = new Collection($authors, new AuthorTransformer, 'authors'); return response()->json( $fractal->createData($resource)->toArray(), 200, ['content-type' => 'application/vnd.api+json'] );}

Rob Allen ~ @akrabat

This is a standard controller action
We pass in the Fractal Manager and let the Service Container do its job
Stock Eloquent call to get our list of authors
Page 59: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Create JSON-API responsepublic function list(Manager $fractal) : Response{ $authors = Author::all();

$resource = new Collection($authors, new AuthorTransformer, 'authors'); return response()->json( $fractal->createData($resource)->toArray(), 200, ['content-type' => 'application/vnd.api+json'] );}

Rob Allen ~ @akrabat

Page 60: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Create JSON-API responsepublic function list(Manager $fractal) : Response{ $authors = Author::all();

$resource = new Collection($authors, new AuthorTransformer, 'authors'); return response()->json( $fractal->createData($resource)->toArray(), 200, ['content-type' => 'application/vnd.api+json'] );}

Rob Allen ~ @akrabat

Page 61: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Create JSON-API responsepublic function list(Manager $fractal) : Response{ $authors = Author::all();

$resource = new Collection($authors, new AuthorTransformer, 'authors'); return response()->json( $fractal->createData($resource)->toArray(), 200, ['content-type' => 'application/vnd.api+json'] );}

Rob Allen ~ @akrabat

Page 62: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Create JSON-API responsepublic function list(Manager $fractal) : Response{ $authors = Author::all();

$resource = new Collection($authors, new AuthorTransformer, 'authors'); return response()->json( $fractal->createData($resource)->toArray(), 200, ['content-type' => 'application/vnd.api+json'] );}

Rob Allen ~ @akrabat

Page 63: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Create JSON-API responsepublic function list(Manager $fractal) : Response{ $authors = Author::all();

$resource = new Collection($authors, new AuthorTransformer, 'authors'); return response()->json( $fractal->createData($resource)->toArray(), 200, ['content-type' => 'application/vnd.api+json'] );}

Rob Allen ~ @akrabat

Page 64: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Output$ curl http://localhost:8888/authors/4{ "data": [ { "type": "authors", "id": "1", "attributes": { "name": "Suzanne Collins", }, "links": { "self": "/authors/1" } },

Rob Allen ~ @akrabat

Page 65: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

When things go wrong

Rob Allen ~ @akrabat

Page 66: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Default error output$ curl http://localhost:8888/authors/999<!DOCTYPE html><html> <head> <meta name="robots" content="noindex,nofollow" /> <style> /* Copyright (c) 2010, Yahoo! Inc. All rights reserved. Code licensed under the html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,p html { background: #eee; padding: 10px } img { border: 0; } #sf-resetcontent { width:970px; margin:0 auto; }

Rob Allen ~ @akrabat

Page 67: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Great error handling• Error representations are first class citizens• Provides application error code & human readable message• Pretty prints for the humans!

Rob Allen ~ @akrabat

Page 68: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Override ExceptionsHandler::render()public function render($request, Exception $e){ $statusCode = $this->getStatusCodeFromException($e); $error['error'] = Response::$statusTexts[$statusCode]; if (env('APP_DEBUG')) { $error['message'] = $e->getMessage(); $error['file'] = $e->getFile() . ':' . $e->getLine(); $error['trace'] = explode("\n", $e->getTraceAsString()); }

return response()->json($error, $statusCode, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);}

Rob Allen ~ @akrabat

Page 69: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Override ExceptionsHandler::render()public function render($request, Exception $e){ $statusCode = $this->getStatusCodeFromException($e); $error['error'] = Response::$statusTexts[$statusCode]; if (env('APP_DEBUG')) { $error['message'] = $e->getMessage(); $error['file'] = $e->getFile() . ':' . $e->getLine(); $error['trace'] = explode("\n", $e->getTraceAsString()); }

return response()->json($error, $statusCode, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);}

Rob Allen ~ @akrabat

Page 70: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Override ExceptionsHandler::render()public function render($request, Exception $e){ $statusCode = $this->getStatusCodeFromException($e); $error['error'] = Response::$statusTexts[$statusCode]; if (env('APP_DEBUG')) { $error['message'] = $e->getMessage(); $error['file'] = $e->getFile() . ':' . $e->getLine(); $error['trace'] = explode("\n", $e->getTraceAsString()); }

return response()->json($error, $statusCode, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);}

Rob Allen ~ @akrabat

Page 71: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Override ExceptionsHandler::render()public function render($request, Exception $e){ $statusCode = $this->getStatusCodeFromException($e); $error['error'] = Response::$statusTexts[$statusCode]; if (env('APP_DEBUG')) { $error['message'] = $e->getMessage(); $error['file'] = $e->getFile() . ':' . $e->getLine(); $error['trace'] = explode("\n", $e->getTraceAsString()); }

return response()->json($error, $statusCode, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);}

Rob Allen ~ @akrabat

Page 72: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Error output (live)$ curl -i http://localhost:8888/authors/999HTTP/1.0 404 Not FoundContent-Type: application/json

{ "error": "Not Found"}

Rob Allen ~ @akrabat

Page 73: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Error output (debug)$ curl -i http://localhost:8888/authors/999HTTP/1.0 404 Not FoundContent-Type: application/json

{ "error": "Not Found", "message": "No query results for model [App\\Author] 999", "file": "vendor/illuminate/database/Eloquent/Builder.php:331", "trace": [ "#0 vendor/illuminate/database/Eloquent/Model.php(1509): Illuminate\\Database\\Eloquent\\Builder->findOrFail(999)", "#1 vendor/illuminate/database/Eloquent/Model.php(1521): Illuminate\\Database\\Eloquent\\Model->__call('findOrFail', Array)", "#2 app/Http/Controllers/AuthorController.php(30): Illuminate\\Database\\Eloquent\\Model::__callStatic('findOrFail', Array)",…

Rob Allen ~ @akrabat

Page 74: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

To sum up

Rob Allen ~ @akrabat

Page 75: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Resources• https://github.com/akrabat/lumen-bookshelf-api• https://lumen.laravel.com/docs/• https://fractal.thephpleague.com

Books:• Build APIs You Won't Hate by Phil Sturgeon• RESTful Web APIs by L. Richardson, M. Amundsen & S. Ruby

Rob Allen ~ @akrabat

Page 76: Writing APIs in Lumen - akrabat.com · Content negotiation. Correctly parse the request • Read the Content-Type header • Raise "415 Unsupported media type" status if unsupported

Thank you!

Rob Allen ~ @akrabat