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

REST easy with API platform! #DevConMU

REST easy with API platform! #DevConMU

The web has changed! Users spend more time on mobile than on desktops and they expect to have an amazing user experience on both platforms. APIs are the heart of the new web as the central point of access data, encapsulating logic and providing the same data and same features for desktops and mobiles.

In this talk, I will show you how in only 45 minutes we can create full REST API, with documentation and admin application build with React.

Antonio Peric-Mazar

April 12, 2019
Tweet

More Decks by Antonio Peric-Mazar

Other Decks in Programming

Transcript

  1. REST easy with API
    Platform
    Antonio Perić-Mažar, Locastic

    12.04.2019., #DevConMu

    View Slide

  2. View Slide

  3. Antonio Perić-Mažar
    CEO @ Locastic
    Co-Founder @ Blockada
    [email protected]
    @antonioperic

    View Slide

  4. Locastic
    • We help clients create amazing web and mobile apps (since 2011)
    • mobile development
    • web development
    • UX/UI
    • Training and Consulting
    • Shift Conference, Symfony Croatia
    • www.locastic.com t: @locastic

    View Slide

  5. View Slide

  6. Why API?

    View Slide

  7. The web has changed
    • Javascript web apps are standard (SPA)
    • Users spend more time on using mobile devices than desktop or TV.
    • Linked Data and the semantic web are a reality

    View Slide

  8. View Slide

  9. APIs are the heart of this new web
    • Central point to access data (R/W data)
    • Encapsulate business logic
    • Same data and same features for desktops, mobiles, TVs and etc
    • It is stateless (PHP Sessions make horizontal scaling harder)

    View Slide

  10. Client Apps
    • HTML5 (SPA), mobile apps, TVs, Cars etc.
    • Holds all the presentation logic
    • Is downloaded first (SPA, shell model)
    • Queries the API to retrieve and modify data using asynchronous requests
    • Is 100% composed of HTML, JavaScript and assets (CSS and etc)
    • Can be hosted on a CDN

    View Slide

  11. View Slide

  12. Immediate benefits
    • Speed (even on mobile)
    • Scalability and robustness
    • Development comfort
    • Long term benefits

    View Slide

  13. Formats, standards, patterns

    View Slide

  14. HTTP + REST + JSON
    • Work everywhere
    • Lightweight
    • Stateless
    • HTTP has a powerful caching model
    • Extensible (JSON-LD, Hydra, Swagger, HAL…)
    • High quality tooling

    View Slide

  15. HATEOAS / Linked Data
    • Hypermedia as the Engine of Application State
    • Hypermedia: IRI as identifier
    • Ability to reference external data (like hypertext links)
    • Auto discoverable <=> Generic clients

    View Slide

  16. View Slide

  17. JSON-LD (JSON for Linked Data)
    • Standard: W3C recommandation (since 2014)
    • Machine readable data
    • Easy to use: looks like a typical JSON document
    • Already used by Gmail, GitHub, BBC, Microsoft, US gov…
    • Compliant with technologies of the semantic web: RDF, SPARQL, triple
    store…
    • Good for SEO

    View Slide

  18. Hydra
    • Describe REST APIs in JSON-LD
    • = write support
    • = auto-discoverable APIs
    • = standard for collections, paginations, errors, filters
    • Draft W3C (Work In Progress)

    View Slide

  19. {
    "@context": "/contexts/Book",
    "@id": "/books/2",
    "@type": "http://schema.org/Book",
    "id": 2,
    "isbn": "9790070863971",
    "description": "A long but very interesting story about REST and asyncio.",
    "author": "The life!",
    "title": "X",
    "publicationDate": "2002-01-29T00:00:00+00:00"
    }

    View Slide

  20. {
    "@context": "contexts/Errors",
    "@type": “hydra:Error”,
    “hydra:title”: “An error occurred”,
    “hydra:description”: “Not found”
    }

    View Slide

  21. {
    "@context": "/contexts/Book",
    "@id": "/books",
    "@type": "hydra:Collection",
    "hydra:member": [
    {
    "@id": "/books/2",
    "@type": "http://schema.org/Book",
    "id": 2,
    "isbn": "9790070863971",
    "description": "A long but very interesting story about REST and asyncio.",
    "author": "The life!",
    "title": "X",
    "publicationDate": "2002-01-29T00:00:00+00:00"
    },

    {
    "@id": "/books/31",
    "@type": "http://schema.org/Book",
    "id": 31,
    "isbn": "9791943452827",
    "description": "Tempora voluptas ut dolorem voluptates. Provident natus ipsam fugiat est ipsam quia. Sint mollitia sed facere qui
    sit. Ad iusto molestias iusto autem laboriosam nulla earum eius.",
    "author": "Miss Gladyce Nader I",
    "title": "Voluptas doloremque esse dolor qui illo placeat harum voluptatem.",
    "publicationDate": "1970-10-11T00:00:00+00:00"
    }
    ],
    "hydra:totalItems": 125,
    "hydra:view": {
    "@id": "/books?page=1",
    "@type": "hydra:PartialCollectionView",
    "hydra:first": "/books?page=1",
    "hydra:last": "/books?page=5",
    "hydra:next": "/books?page=2"
    }
    }

    View Slide

  22. “API Platform is the most
    advanced API platform, in any
    framework or language.”
    Fabien Potencier, SymfonyCon 2017

    View Slide

  23. View Slide

  24. API Platform: the promise
    • Fully featured API supporting Swagger + JSON-LD + Hydra + HAL in minutes
    • An auto generated doc
    • Convenient API spec and test tools using Behat
    • Easy authentication management with JWT or OAuth
    • CORS and HTTP cache
    • All the tools you love: Doctrine ORM, Monolog, Swiftmailer...

    View Slide

  25. API Platform <3 Symfony
    • Built on top of Symfony full-stack
    • Install any existing SF bundles
    • Follow SF Best Practices
    • Use your Symfony skills
    • Can be used in your existing SF app
    • (Optional) tightly integrated with Doctrine

    View Slide

  26. Features
    • CRUD
    • Filters
    • Serializations groups and relations
    • Validation
    • Pagination
    • Sorting
    • The event system
    • Content Negation
    • Extensions
    • HTTP and reverse proxy caching
    • Invalidation-based HTTP caching
    • JS Admin apps
    • GraphQL support
    • And basically everything needed to build
    modern APIs

    View Slide

  27. How to start?
    • Download distribution and use Docker (includes frontend applications, etc)
    • https://github.com/api-platform/api-platform/releases/tag/v2.4.2
    • Use Symfony Flex and any setup you want (little harder to setup)
    • Enjoy!!!

    View Slide

  28. CRUD
    namespace App\Entity;
    class Author
    {
    private $id;
    private $firstName;
    private $lastName;
    // ...
    }
    # api/config/packages/api_platform/
    author.yaml
    resources:
    App\Entity\Author:~

    View Slide

  29. View Slide

  30. schema.org

    View Slide

  31. View Slide

  32. View Slide

  33. View Slide

  34. {
    "@context": "http://schema.org",
    "@type": "FlightReservation",
    "reservationNumber": "RXJ34P",
    "reservationStatus": "http://schema.org/Confirmed",
    "underName": {
    "@type": "Person",
    "name": "Eva Green"
    },
    "reservationFor": {
    "@type": "Flight",
    "flightNumber": "110",
    "airline": {
    "@type": "Airline",
    "name": "United",
    "iataCode": "UA"
    },
    "departureAirport": {
    "@type": "Airport",
    "name": "San Francisco Airport",
    "iataCode": "SFO"
    },
    "departureTime": "2017-03-04T20:15:00-08:00",
    "arrivalAirport": {
    "@type": "Airport",
    "name": "John F. Kennedy International Airport",
    "iataCode": "JFK"
    },
    "arrivalTime": "2017-03-05T06:30:00-05:00"
    }
    }

    View Slide

  35. Using schema.org in Api Platform
    resources:
    App\Entity\FlightReservation:
    iri: 'http://schema.org/FlightReservation'

    View Slide

  36. Using schema.org in Api Platform
    resources:
    App\Entity\FlightReservation:
    iri: 'http://schema.org/FlightReservation'
    properties:
    status:
    iri: 'http://schema.org/reservationStatus'

    View Slide

  37. Operations
    • API Platform Core relies on the concept of operations. Operations can be
    applied to a resource exposed by the API. From an implementation point of
    view, an operation is a link between a resource, a route and its related
    controller.
    • There are two types of operations:
    • Collection operations act on a collection of resources. By default two
    routes are implemented: POST and GET.
    • Item operations act on an individual resource. 3 default routes are
    defined GET, PUT and DELETE.

    View Slide

  38. View Slide

  39. Custom operation
    class Match
    {
    private $id;
    /**
    * @Groups({"match_read"})
    */
    private $datetime;
    /**
    * @Groups({"match_read", "match_write"})
    */
    private $playerOnePoints;
    /**
    * @Groups({"match_read", "match_write"})
    */
    private $playerTwoPoints;
    /**
    * @Groups({"match_read", "match_write"})
    */
    private $playerOne;
    /**
    * @Groups({"match_read", "match_write"})
    */
    private $playerTwo;
    /**
    * @Groups({"match_read"})
    */
    private $winner;
    /**
    * @Groups({"match_read"})
    */
    private $result;
    }

    View Slide

  40. Custom operation
    class Match
    {
    private $id;
    /**
    * @Groups({"match_read"})
    */
    private $datetime;
    /**
    * @Groups({"match_read", "match_write"})
    */
    private $playerOnePoints;
    /**
    * @Groups({"match_read", "match_write"})
    */
    private $playerTwoPoints;
    /**
    * @Groups({"match_read", "match_write"})
    */
    private $playerOne;
    /**
    * @Groups({"match_read", "match_write"})
    */
    private $playerTwo;
    /**
    * @Groups({"match_read"})
    */
    private $winner;
    /**
    * @Groups({"match_read"})
    */
    private $result;
    }
    class MatchController extends Controller
    {
    /**
    * @param Match $data
    *
    * @return Match
    */
    public function getMatchAction($data)
    {
    $result = $data->getPlayerOne() . ' ' . $data->getPlayerOnePoints().':'
    .$data->getPlayerTwoPoints() . ' ' . $data->getPlayerTwo();
    $data->setResult($result);
    return $data;
    }
    }

    View Slide

  41. Custom operation
    class Match
    {
    private $id;
    /**
    * @Groups({"match_read"})
    */
    private $datetime;
    /**
    * @Groups({"match_read", "match_write"})
    */
    private $playerOnePoints;
    /**
    * @Groups({"match_read", "match_write"})
    */
    private $playerTwoPoints;
    /**
    * @Groups({"match_read", "match_write"})
    */
    private $playerOne;
    /**
    * @Groups({"match_read", "match_write"})
    */
    private $playerTwo;
    /**
    * @Groups({"match_read"})
    */
    private $winner;
    /**
    * @Groups({"match_read"})
    */
    private $result;
    }
    class MatchController extends Controller
    {
    /**
    * @param Match $data
    *
    * @return Match
    */
    public function getMatchAction($data)
    {
    $result = $data->getPlayerOne() . ' ' . $data->getPlayerOnePoints().':'
    .$data->getPlayerTwoPoints() . ' ' . $data->getPlayerTwo();
    $data->setResult($result);
    return $data;
    }
    }
    # app/config/routing.yml
    get_match:
    path: /api/v1/matches/{id}.{_format}
    methods: ['GET']
    defaults:
    _controller: AppBundle:Match:getMatch
    _api_resource_class: AppBundle\Entity\Match
    _api_item_operation_name: get

    View Slide

  42. Override default order
    resources:
    App\Entity\Player:
    attributes:
    order:
    lastname: ‘ASC’

    View Slide

  43. Adding subresource
    resources:
    AppBundle\Entity\Player:
    properties:
    matches:
    subresource:
    resourceClass: ‘App\Entity\Match’
    maxDepth: 1
    collection: true
    -> api.com/players/1/matches

    View Slide

  44. Filters
    • If Doctrine ORM support is enabled, adding filters is as easy as registering
    a filter service in your api/config/services.yml file and adding an
    attribute to your resource configuration.
    • Filters add extra conditions to base database query
    • Useful filters for the Doctrine ORM are provided with the library. You can
    also create custom filters that would fit your specific needs.

    View Slide

  45. Filters
    • Search filter (partial, start, end, exact, ipartial, iexact)
    • Date filter (?property[]=value )
    • Boolean filter (?property=[true|false|1|0])
    • Numeric filter (?property=int|bigint|decimal)
    • Range filter (?property[lt]|[gt]|[lte]|[gte]|[between]=value)
    • Order filter (?order[property]=)
    • …
    • Custom filters

    View Slide

  46. Filters examples
    # AppBundle/Resources/config/api_resources/resources.yml
    resources:
    AppBundle\Entity\Player:
    # ...
    attributes:
    filters: ['player.search', 'player.order']
    AppBundle\Entity\Match:
    # ...
    attributes:
    filters: ['match.date']
    services:
    player.search_filter:
    parent: 'api_platform.doctrine.orm.search_filter'
    arguments: [ { id: 'exact', email: 'exact', firstName: 'partial' } ]
    tags: [ { name: 'api_platform.filter', id: 'player.search' } ]
    match.date_filter:
    parent: 'api_platform.doctrine.orm.date_filter'
    arguments: [ { datetime: ~ } ]
    tags: [ { name: 'api_platform.filter', id: 'match.date' } ]
    player.order_filter:
    parent: 'api_platform.doctrine.orm.order_filter'
    arguments: [{ firstName: 'ASC', lastName: 'ACS', email: ~ }]
    tags: [{ name: 'api_platform.filter', id: 'player.order' }]

    View Slide

  47. Serialization Groups
    • API Platform Core allows to choose which attributes of the resource are
    exposed during the normalization (read) and denormalization (write)
    process. It relies on the serialization (and deserialization) groups feature of
    the Symfony Serializer component.
    • allows to specify the definition of serialization using XML, YAML, or
    annotations.

    View Slide

  48. Serialization Groups

    View Slide

  49. namespace App\Entity;
    use FOS\UserBundle\Model\User as BaseUser;
    use Symfony\Component\Serializer\Annotation\Groups;
    class Player extends BaseUser
    {
    /**
    * @var int
    */
    protected $id;
    /**
    * @Groups({"player_read", "player_write"})
    */
    private $firstName;
    /**
    * @Groups({"player_read", "player_write"})
    */
    private $lastName;
    /**
    * @Groups({"player_read", "player_write"})
    */
    protected $email;
    // ...
    }
    # UserBundle/Resources/api_resources/resources.yml
    resources:
    App\Entity\Player:
    attributes:
    normalization_context:
    groups: ['player_read']
    denormalization_context:
    groups: ['player_write']

    View Slide

  50. Using Different Serialization
    Groups per Operation
    # UserBundle/Resources/api_resources/resources.yml
    resources:
    App\Entity\Player:
    itemOperations:
    get:
    method: 'GET'
    normalization_context:
    groups: ['player_read', 'player_extra']
    put:
    method: 'PUT'
    delete:
    method: 'DELETE'
    attributes:
    normalization_context:
    groups: ['player_read']
    denormalization_context:
    groups: ['player_write']

    View Slide

  51. Api platform events

    View Slide

  52. // src/AppBundle/EventSubscriber/MatchEventSubscriber.php
    class MatchEventSubscriber implements EventSubscriberInterface
    {
    private $matchHelper;
    public function __construct(MatchHelper $matchHelper)
    {
    $this->matchHelper = $matchHelper;
    }
    public static function getSubscribedEvents()
    {
    return [
    KernelEvents::VIEW => [['addWinner', EventPriorities::POST_VALIDATE]],
    ];
    }
    public function addWinner(GetResponseForControllerResultEvent $event)
    {
    $match = $event->getControllerResult();
    $method = $event->getRequest()->getMethod();
    if(!$match instanceof Match || $method !== 'POST') {
    return;
    }
    $winner = $this->matchHelper->getWinner($match);
    $match->setWinner($winner);
    }
    }

    View Slide

  53. Extensions
    • API Platform Core provides a system to extend queries on items and
    collections.
    • Custom extensions must implement
    the ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\Query
    CollectionExtensionInterface and / or
    the ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\Query
    ItemExtensionInterface interfaces, to be run when querying for a
    collection of items and when querying for an item respectively.

    View Slide

  54. class GetPlayersExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
    {
    public function applyToItem(
    QueryBuilder $queryBuilder,
    QueryNameGeneratorInterface $queryNameGenerator,
    string $resourceClass,
    array $identifiers,
    string $operationName = null,
    array $context = []
    ) {
    $this->addWhere($queryBuilder, $resourceClass, $operationName);
    }
    public function applyToCollection(
    QueryBuilder $queryBuilder,
    QueryNameGeneratorInterface $queryNameGenerator,
    string $resourceClass,
    string $operationName = null
    ) {
    $this->addWhere($queryBuilder, $resourceClass, $operationName);
    }
    private function addWhere(QueryBuilder $queryBuilder, string $resourceClass, string $operationName = null)
    {
    if ($resourceClass != Player::class || $operationName != 'get') {
    return;
    }
    $rootAlias = $queryBuilder->getRootAliases()[0];
    $queryBuilder->andWhere(
    $queryBuilder->expr()->eq($rootAlias.'.enabled', ':enabled')
    )->setParameter('enabled', true);
    }
    }

    View Slide

  55. services:
    app.extension.get_players:
    class: AppBundle\Doctrine\ORM\Extension\GetPlayersExtension
    public: false
    tags:
    - { name: api_platform.doctrine.orm.query_extension.collection, priority: 9 }
    - { name: api_platform.doctrine.orm.query_extension.item }

    View Slide

  56. Per Resource Authorization Mechanism
    namespace AppBundle\Entity;
    use ApiPlatform\Core\Annotation\ApiResource;
    use Doctrine\ORM\Mapping as ORM;
    /**
    * @ApiResource(
    * attributes={"is_granted"="has_role('ROLE_ADMIN')"},
    * itemOperations={
    * "get"={"method"="GET", "is_granted"="object.getOwner() == user"}
    * }
    * )
    * @ORM\Entity
    */
    class Secured
    {
    /**
    * @ORM\Column(type="integer")
    * @ORM\Id
    * @ORM\GeneratedValue(strategy="AUTO")
    */
    public $id;
    /**
    * @ORM\Column(type="text")
    */
    public $owner;

    View Slide

  57. Cache invalidation is builtin

    View Slide

  58. Translation Support
    • https://github.com/Locastic/ApiPlatformTranslationBundle
    • https://locastic.com/blog/having-troubles-with-implementing-translations-in-
    apiplatform/

    View Slide

  59. GraphQL support

    View Slide

  60. GraphQL Support
    • docker-compose exec php composer req webonyx/graphql-php && docker-
    compose exec php bin/console cache:clear
    • The GraphQL implementation supports queries, mutations, 100% of the
    Relay server specification, pagination, filters and access control rules. You
    can use it with the popular RelayJS and Apollo clients.

    View Slide

  61. View Slide

  62. Testing
    • PHPUnit
    • Postman (newman)
    • Panther
    • https://locastic.com/blog/what-to-do-when-you-get-lost-in-api-testing/

    View Slide

  63. Mercure

    View Slide

  64. Mercure
    • Fast, written in Go
    • native browser support, no lib nor SDK required (built on top of HTTP and server-sent events)
    • compatible with all existing servers, even those who don't support persistent connections
    (serverless architecture, PHP, FastCGI...)
    • Automatic HTTP/2 and HTTPS (using Let's Encrypt) support
    • CORS support, CSRF protection mechanism
    • Cloud Native, follows the Twelve-Factor App methodology
    • Open source (AGPL)
    • …

    View Slide

  65. Let’s try it
    https://symfonycon.les-tilleuls.coop/

    View Slide

  66. Update progressive web app
    • docker-compose exec client generate-api-platform-client
    • Follow instructions

    View Slide

  67. Version 2.4
    • read and write support for MongoDB, the reference document database,
    including a lot of useful filters
    • Read support for Elasticsearch, the open source search and analytics
    engine, including filters for advanced search
    • Automatic “push” of updated resources from the server to the clients using
    the brand new Mercure protocol

    View Slide

  68. Version 2.4
    • Integration with the Symfony Messenger component to easily implement the
    CQRS pattern and to handle messages asynchronously (using brokers such as
    RabbitMQ, Apache Kafka, Amazon SQS or Google PubSub)
    • Ability to leverage the “Server Push” feature of HTTP/2 to preemptively send the
    relations of a requested resource to the client
    • Automatic availability of list filters in the React-based admin when a corresponding
    one is available API-side
    • Full compatibility with the version 3 of the OpenAPI specification format (formerly
    known as Swagger), and integration of the beautiful ReDoc documentation generator

    View Slide

  69. Version 2.4
    • Improved DTOs support
    • Per resource configuration of HTTP cache headers
    • Ability to easily use the Sunset HTTP header to advertise the removal date
    of deprecated endpoints

    View Slide

  70. View Slide

  71. GFNY Ticketing system
    • 20 races, 18 countries
    • Different timezones, currencies, languages…
    • More then 60 000 tickets per year, with very complex login
    • Standings and rankings
    • Email notification, flexibility
    • Etc.

    View Slide

  72. Conclusion
    • Very powerful, even for large applications
    • Easy to start, well documented
    • Active community and company behind it
    • Write tests!

    View Slide

  73. Thank you!

    View Slide

  74. [email protected]
    @antonioperic
    www.locastic.com
    Questions?

    View Slide