$30 off During Our Annual Pro Sale. View Details »

REST More With JSON-API (VarnaConf 2017)

REST More With JSON-API (VarnaConf 2017)

Covers what is an open standard, what standards we use in our day to day work. Describes in detail what JSON-API is and how utilizing it can help us build more maintainable and easier to consume APIs.

@ VarnaConf 2017

Boyan Yordanov

August 26, 2017
Tweet

More Decks by Boyan Yordanov

Other Decks in Programming

Transcript

  1. VarnaConf 2017
    REST MORE WITH JSON:API AND FRACTAL
    Use open standards to build better and more
    maintainable APIs
    1

    View Slide

  2. $> WHOAMI
    $> BOYAN
    Developer at Shtrak
    Community cheerleader
    Newbie speaker and writer
    Organizer of PHP Varna
    2 . 1

    View Slide

  3. WHERE TO FIND ME
    @specter_bg
    boyanyordanov
    boyanyordanov
    2 . 2

    View Slide

  4. WHAT IS REST ?
    REpresentational State Transfer
    3 . 1

    View Slide

  5. WHAT IS REST ?
    pretty URLs ?
    HTTP verbs for actions ?
    None of those ?
    3 . 2

    View Slide

  6. ACHIEVING ETERNAL GLORY
    https://martinfowler.com/articles/richardsonMaturityMo
    3 . 3

    View Slide

  7. HOW OFTEN DO YOU SEE
    POST /article/1/delete
    POST /article/1/publish
    GET /get-published-articles
    3 . 4

    View Slide

  8. OR EVEN WORSE
    GET /REST/article/create?title="No REST from bad design"
    3 . 5

    View Slide

  9. SO ARE WE ... ?
    ... probably NOT. But does it really matter?
    3 . 6

    View Slide

  10. 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

    View Slide

  11. EXAMPLES WE USE EVERYDAY
    HTTP
    CSS, HTML, ECMAScript
    C, C++, Java ( ), C#
    PHP RFC process and PHP-Fig PSRs (sort of)
    JCP
    4 . 2

    View Slide

  12. 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
    Other
    GraphQL
    5

    View Slide

  13. BENEFITS OF USING STANDARDS FOR APIS
    easier to design and build
    easier to maintain
    easier to consume
    easier to test
    6 . 1

    View Slide

  14. 6 . 2

    View Slide

  15. 7

    View Slide

  16. 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

    View Slide

  17. http://www.commitstrip.com/en/2017/02/20/no-ones-
    fault/
    9

    View Slide

  18. {
    "data": {
    "type": "articles",
    "id": "1",
    "attributes": {
    "title": "JSON API paints my bikeshed!"
    },
    "relationships": {
    "author": {
    "links": {"self": "...", "related": "..."},
    "data": { "type": "people", "id": "9" }
    }
    },
    "links": { "self": "/articles/1"}
    }
    }
    10

    View Slide

  19. 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

    View Slide

  20. DECONSTRUCTING THE COMPOUND DOCUMENT
    TOP LEVEL ATTRIBUTES
    data
    errors
    meta
    links
    jsonapi
    included
    11 . 2

    View Slide

  21. TYPES OF OBJECTS
    resource object
    link object
    resource identi er object
    error object
    12 . 1

    View Slide

  22. THE RESOURCE OBJECT
    {
    "type": "meetups",
    "id": "1",
    "attributes": {
    "name": "PHP Varna",
    "description": "The php UG of Varna",
    "next-event": "2017-05-18T19:30:00.200Z"
    }
    }
    12 . 2

    View Slide

  23. LINKS OBJECT
    "links": {
    "self": "/meetups/1"
    },
    "data": {
    ...
    }
    12 . 3

    View Slide

  24. "data": {
    ...
    },
    "relationships": {
    "links": {
    "events": {
    "related": "/meetups/1/events",
    "meta": {
    "count": 50
    }
    }
    }
    }
    12 . 4

    View Slide

  25. RESOURCE IDENTIFIER OBJECT
    {
    "id": "1",
    "type": "events",
    "meta": {
    "attendee-count": 50
    }
    }
    12 . 5

    View Slide

  26. RELATIONSHIPS OBJECT
    "relationships": {
    "location": {
    "links": {
    "self": "events/1/relationships/venue",
    "related": "events/1/venue"
    },
    "data": { "type": "venues", "id": "1" }
    }
    }
    12 . 6

    View Slide

  27. 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-event": "2017-05-18T19:30:00.200Z"
    },
    "relationships": {
    "organizer": {
    "data": { "type": "users", "id": "1" }
    }
    }
    }]
    }
    13

    View Slide

  28. RELATIONSHIPS
    Change the meetup place
    PATCH /events/123/relationships/venue HTTP/1.1
    Content-Type: application/vnd.api+json
    Accept: application/vnd.api+json
    {
    "data": { "type": "venues", "id": "12" }
    }
    14 . 1

    View Slide

  29. Remove the venue (untill we nd a new one?)
    PATCH /events/123/relationships/venue HTTP/1.1
    Content-Type: application/vnd.api+json
    Accept: application/vnd.api+json
    {
    "data": null
    }
    14 . 2

    View Slide

  30. SIDELOADING RELATIONSHIPS (GET MEETUP AND VENUE)
    GET /events/1?include=venue HTTP/1.1
    HTTP/1.1 200 OK
    Content-Type: application/vnd.api+json
    {
    "data": {
    // resource object for the event
    },
    "included": [
    {
    "type": "venues",
    "id": "1",
    "attributes": {
    "name": "VarnaLab",
    "address": "ul. 'Pencho Slaveykov' 50"
    }
    }
    ]
    14 . 3

    View Slide

  31. 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

    View Slide

  32. QUERY PARAMS
    ltering
    sorting
    GET /meetups?filter[location]="Varna" 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

    View Slide

  33. 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

    View Slide

  34. COMPLEX QUERY
    GET /events?filter[meetup]="PHP Varna"
    &filter[venue]="VarnaLab"
    &filter[date_after]="26-08-2017"
    &include=venues
    &fields[events]=name,date
    &fields[venues]=address HTTP/1.1
    Accept: application/vnd.api+json
    16 . 3

    View Slide

  35. PAGINATION
    GET /meetups?page[number]=3&page[size]=1 HTTP/1.1
    Accept: application/vnd.api+json
    16 . 4

    View Slide

  36. {
    "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 . 5

    View Slide

  37. RESOURCE OPERATIONS
    listing
    get individual resource
    create
    update
    delete
    17

    View Slide

  38. 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 . 1

    View Slide

  39. RESPONSE
    HTTP/1.1 200 OK
    Content-Type: application/vnd.api+json
    {
    "data": {
    "type": "meetups",
    "id": 3,
    "attributes": {
    "name": "JS Varna?",
    "desription": "Does Varna need another meetup?"
    }
    }
    }
    18 . 2

    View Slide

  40. UPDATING A MEETUP
    PATCH /meetups/3 HTTP/1.1
    Content-Type: application/vnd.api+json
    Accept: application/vnd.api+json
    {
    "data": {
    "type": "meetups",
    "attributes": {
    "name": "JS Varna",
    "desription": "Happening soon in a hackerspace near you"
    }
    }
    }
    18 . 3

    View Slide

  41. CANCELING AN EVENT
    RESPONSE
    DELETE /events/123
    Content-Type: application/vnd.api+json
    Accept: application/vnd.api+json
    HTTP/1.1 204 No Content
    Content-Type: application/vnd.api+json
    18 . 4

    View Slide

  42. A 404 ERROR
    RESPONSE
    GET /meetups/1234
    Content-Type: application/vnd.api+json
    Accept: application/vnd.api+json
    HTTP/1.1 404 Not Found
    Content-Type: application/vnd.api+json
    {
    "errors": [
    {
    "title": "Resource Not Found",
    "content": "There is no event with id 1234"
    }
    ]
    }
    18 . 5

    View Slide

  43. FRACTAL
    Like a view layer for your
    JSON/YAML/etc.
    19

    View Slide

  44. MAIN CONCEPTS
    Fractal Manager
    Resource
    Includes
    Serializer
    Transformer
    Pagination
    via paginator
    via cursor
    20

    View Slide

  45. 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

    View Slide

  46. 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

    View Slide

  47. 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

    View Slide

  48. 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

    View Slide

  49. 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

    View Slide

  50. 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

    View Slide

  51. WHAT IS MISSING FROM THE SPEC?
    ltering
    sorting
    sparse eldsets
    pagination - present and usable but not spec
    compliant
    22 . 1

    View Slide

  52. 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

    View Slide

  53. SUPPORTED ADAPTERS
    Laravel
    Doctrine
    Zend Framework
    Phalcon
    Pagerfanta
    your own PaginatorInterface implementation
    22 . 3

    View Slide

  54. WHAT IS NOT INCLUDED ON PURPOSE?
    content negotiation
    response code handling
    error objects
    22 . 4

    View Slide

  55. HANDLING ERROR OBJECTS
    23

    View Slide

  56. Use a base class
    class JsonApiError extends \Exception {
    protected $status;
    protected $title;
    protected $detail;
    protected $source;
    protected $meta;
    public function toArray() {...}
    }
    24 . 1

    View Slide

  57. Extend it for speci c errors
    class NotFoundError extends JsonApiError {
    protected $status = 404;
    protected $title = 'Resource not found';
    }
    24 . 2

    View Slide

  58. EASIER FRACTAL
    composer install spatie/fractalistic
    A developer friendly wrapper around
    Fractal
    25 . 1

    View Slide

  59. Fractal::create()
    ->collection($meetups)
    ->transformWith(new MeetupTransformer())
    ->includeVenue()
    ->toArray();
    25 . 2

    View Slide

  60. ALTERNATIVES
    neomerx/json-api
    resource transformation
    query parameters handling
    errors
    26

    View Slide

  61. RESOURCES
    http://jsonapi.org/
    http://fractal.thephpleague.com/
    https://www.ics.uci.edu/~ elding/pubs/dissertation/ e
    Steve Klabnik's talk: past, present and future of json-ap
    https://youtu.be/Foi54om6oGQ
    discussion: who's using json-api - https://github.com/jso
    api/issues/825
    27

    View Slide

  62. THANK YOU
    28

    View Slide

  63. QUESTIONS ?
    29

    View Slide