Upgrade to Pro — share decks privately, control downloads, hide ads and more …

REST More with JSON-API and Fractal

REST More with JSON-API and Fractal

How we can use open standards and specifications to design and build RESTful APIs that are easier to developer, maintain and use. Focusing on JSON-API (http://jsonapi.org/) and the Fractal php package (http://fractal.thephpleague.com/)

Given @ PHP Varna User Group, May 2017

Avatar for Boyan Yordanov

Boyan Yordanov

May 18, 2017
Tweet

More Decks by Boyan Yordanov

Other Decks in Programming

Transcript

  1. REST MORE WITH JSON:API AND FRACTAL Use open standards to

    build better and more maintainable APIs 1
  2. $> WHOAMI $> BOYAN Developer at Shtrak Community cheerleader Wannabe

    speaker and writer Partialy-guilty for phpVarna 2 . 1
  3. WHAT IS REST ? pretty URLs ? HTTP verbs for

    actions ? None of those ? 3 . 2
  4. OPEN STANDARDS What makes an open standard ? Cooperation Adherence

    to Principles Due process, Broad concensus, Transparency, Balance, Openness Collective Empowerment Availability Voluntary Adoption 4 . 1
  5. EXAMPLES WE USE EVERYDAY HTTP CSS, HTML, ECMAScript C, C++,

    Java ( ), C# PHP RFC process and PHP-Fig PSRs (sort of) JCP 4 . 2
  6. OPEN STANDARDS AND APIS Auth and security Oauth 1/2, OpenID

    Connect, JWT Documentation / helper openapi/swagger, api blueprint, RAML json- schema Representation / content types HAL, Problem JSON, Hydra and json-ld, JSON- API 5
  7. BENEFITS OF USING STANDARDS FOR APIS easier to design and

    build easier to maintain easier to consume easier to test 6 . 1
  8. 7

  9. OVERVIEW well-de ned conventions for listing, creating, updating and deleting

    resources recommendations for details not covered in the main spec server and client libraries in most popular languages 8
  10. 9

  11. { "links": { "self": "/meetups/1" }, "data": { "type": "articles",

    "id": "1", "attributes": { "title": "JSON API paints my bikeshed!" }, "relationships": { "author": { "links": {"self": "...", "related": "..."}, "data": { "type": "people", "id": "9" } } } } } 10
  12. THE MEDIA TYPE Client call Server response Accept: application/vnd.api+json Content-Type:

    application/vnd.api+json Content-type: application/vnd.api+json 11 . 1
  13. THE RESOURCE OBJECT { "type": "meetups", "id": "1", "attributes": {

    "name": "PHP Varna", "description": "The php UG of Varna", "next-meetup": "2017-05-18T19:30:00.200Z" } } 12 . 2
  14. LINKS OBJECT "links": { "self": "/meetups/1" } ... "links": {

    "related": { "href": "/meetups/1/attendees", "meta": { "count": 50 } } } 12 . 3
  15. RELATIONSHIPS OBJECT "relationships": { "location": { "links": { "self": "meetups/1/relationships/venue",

    "related": "meetups/1/venue" }, "data": { "type": "venues", "id": "1" } } } 12 . 5
  16. PUTTING IT ALL TOGETHER GET /meetups HTTP/1.1 Accept: application/vnd.api+json {

    "data": { "type": "meetups", "id": "1", "attributes": { "name": "PHP Varna", "description": "The php UG of Varna", "next-meetup": "2017-05-18T19:30:00.200Z" }, "relationships": { "venue": { "data": { "type": "venues", "id": "1" } } } } } 13
  17. RELATIONSHIPS Change the meetup place PATCH /meetups/1/relationships/venue HTTP/1.1 Content-Type: application/vnd.api+json

    Accept: application/vnd.api+json { "data": { "type": "venues", "id": "12" } } 14 . 1
  18. Remove the venue (untill we nd a new one?) PATCH

    /meetups/1/relationships/venue HTTP/1.1 Content-Type: application/vnd.api+json Accept: application/vnd.api+json { "data": null } 14 . 2
  19. SIDELOADING RELATIONSHIPS (GET MEETUP AND VENUE) GET /meetups/1?include=venue HTTP/1.1 HTTP/1.1

    200 OK Content-Type: application/vnd.api+json { "data": { // resource object from previous slides }, "included": [ { "type": "venues", "id": "1", "attributes": { "name": "VarnaLab", "address": "ul. 'Pencho Slaveykov' 50" } } ] 14 . 3
  20. ERROR OBJECTS HTTP/1.1 422 Unprocessable Entity Content-Type: application/vnd.api+json { "errors":

    [ { "status": "422", "source": { "pointer": "/data/attributes/name" }, "title": "Invalid Attribute", "detail": "Meetup name must contain at least three characters." } ] } 15
  21. QUERY PARAMS ltering sorting GET /meetups?filter[venue]="VarnaLab" HTTP/1.1 Accept: application/vnd.api+json GET

    /meetups?sort=name HTTP/1.1 Accept: application/vnd.api+json GET /meetups?sort=-name HTTP/1.1 Accept: application/vnd.api+json 16 . 1
  22. sparse eldsets GET /meetups?fields[meetups]=name HTTP/1.1 Accept: application/vnd.api+json { "data": [

    { "id": "1", "type": "meetups", "attributes": { "name": "PHP Varna" } }, { "id": "2", "type": "meetups", "attributes": { "name": "WordPress Varna" } } 16 . 2
  23. { "meta": { "total-pages": 12 }, "data": [ { "type":

    "meetups", "id": "2", "attributes": { "name": "WordPress Varna", "description": "The place for WordPress entusiasts", "next-meetup": "", } } ], "links": { "self": "/meetups?page[number]=2&page[size]=1", "first": "/meetups?page[number]=1&page[size]=1", "prev": "/meetups?page[number]=2&page[size]=1", "next": "/meetups?page[number]=4&page[size]=1", "last": "/meetups?page[number]=12&page[size]=1" } } 16 . 4
  24. RESOURCE OPERATIONS listing show 404 examples get individual resource create

    show validation errors examples update delete 17
  25. CREATE A MEETUP POST /meetups HTTP/1.1 Content-Type: application/vnd.api+json Accept: application/vnd.api+json

    { "data": { "type": "meetups", "attributes": { "name": "JS Varna?", "desription": "Does Varna need another meetup?" }, "relationships": {//optional} } } 18
  26. RESOURCES & TRANSFORMERS Closure transformer use League\Fractal\Resource\Item; $meetup = retrieveFromDB();

    $meetupResource = new Item($meetup, function($m) { return [ 'id' => $meetup->id, 'name' => $meetup->name, 'description' => $meetup->description, 'next-meetup' => $meetup->events->getNext()->date ]; }); 21 . 1
  27. Class transformer use League\Fractal\TransformerAbstract; use League\Fractal\Resource\Collection; class MeetupTransformer extends TransformerAbstract

    { public function transform(Meetup $meetup) { return [ 'id' => $meetup->id, 'name' => $meetup->name, 'description' => $meetup->description, 'next-meetup' => $meetup->events->getNext()->date ]; } } $data = new Collection($meetups, new MeetupTransformer); 21 . 2
  28. De ning includes use League\Fractal\TransformerAbstract; use League\Fractal\Resource\Collection; class MeetupTransformer extends

    TransformerAbstract { protected $availableIncludes = ['venues']; public function transform($meetup) { ... } public function includeVenue($meetup) { $venue = $meetup->venue; $transformer = new VenueTransformer; return $this->item($venue, $transformer, 'venues'); } } $data = new Collection($meetups, new MeetupTransformer); 21 . 3
  29. Creating a resource use League\Fractal\Serializer\JsonApiSerializer; $manager = new League\Fractal\Manager(); $baseUrl

    = 'http://meetup.dev'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); // ORM call $meetup = Meetup::find(1); // Make a resource out of the data and $resource = new Item($meetup, new MeetupTransformer, 'meetups'); // Run all transformers $manager->createData($resource)->toArray(); 21 . 4
  30. Result [ "data" => [ "type" => "meetups", "id" =>

    "1", "attributes" => [ "name" => "PHP Varna", "description" => "The php UG in Varna", "next-meetup" => "2017-05-18T19:30:00.200Z" ] ], "links" => [ "self" => "http://meetup.dev/meetups/1" ] ] 21 . 5
  31. Using includes use League\Fractal\Serializer\JsonApiSerializer; $manager = new League\Fractal\Manager(); $baseUrl =

    'http://meetup.dev'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); // ORM call $meetup = Meetup::find(1); if (isset($_GET['include'])) { $fractal->parseIncludes($_GET['include']); } 21 . 6
  32. WHAT IS MISSING FROM THE SPEC? ltering sorting sparse eldsets

    pagination - present and usable but not spec compliant 22 . 1
  33. PAGINATION use League\Fractal\Resource\Collection; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use Acme\Model\Book; use Acme\Transformer\BookTransformer; $paginator

    = Meetup::paginate(); $meetups = $paginator->getCollection(); $resource = new Collection($meetups, new MeetupTransformer); $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); 22 . 2
  34. Use a base class class JsonApiError extends \Exception { protected

    $status; protected $title; protected $detail; protected $source; protected $meta; public function toArray() {...} } 24 . 1
  35. Extend it for speci c errors class NotFoundError extends JsonApiError

    { protected $status = 404; protected $title = 'Resource not found'; } 24 . 2
  36. RESOURCES http://jsonapi.org/ http://fractal.thephpleague.com/ https://www.ics.uci.edu/~ elding/pubs/dissertation Steve Klabnik's talk: past, present

    and future of json https://youtu.be/Foi54om6oGQ discussion: who's using json-api - https://github.com api/issues/825 27