Pragmatic

Hypermedia
APIs




A bit of theory first, though


Problem


  • Desktop
  • Smartphones
  • Tablets
  • Smart Watches
  • Glasses
  • Server





  • XMLRPC
  • SOAP
  • RESTful
  • Hypermedia?


Hypermedia APIS


  1. Usage of HTTP to its fullest.
  2. Responses are served as hypermedia
    that manages application state.


~Steve Klabnik                    



Hypermedia APIs


Based on what is proven to work:

The Web.


Vocabulary




Rails-Style REST: RESTful
Real-Style REST: Hypermedia


Goals


  • Loose Coupling
  • Change Resilience
  • Scalability
  • Better Workflow




Three Ingridients


A Pinch of HTTP


  • Methods (please don't call them verbs)
  • Headers
  • Statelessness


¼teaspoon Media Types


  • RFC2046: MIME
  • Based on Drafts for E-Mail
    in 1982
  • Focused on representation,
    not data


One cup of Hyperlinks



A link is a connection from one Web resource to another. Although a simple concept, the link has been one of the primary forces driving the success of the Web.
Link Element, HTML4 spec

                                                       
 




The Practical Part


Design Steps

  1. Analyze Processes
  2. Create a Finite State Machine
  3. Evaluate Hypermedia
  4. Select or Create a Media Type

Processes


Evaluate Hypermedia




Select/Create Media Type


  • HTML
  • Collection+JSON
  • Siren
  • JSON-LD
  • HAL
  • JSON API


Why HAL?


  • Builds on top of established standards.
  • Developed in the Open
  • IETF draft
  • Used in Production
  • Plain JSON + Annotations




Implementation




Enter hyperagent.js



Hyperagent.js

A JavaScript library for consuming HAL hypermedia APIs in the browser


Initializing


var Resource = require('hyperagent').Resource;
var api = new Resource('https://api.example.com/');


The only time we will see a hard-coded URL!


A HAL response

{
  "_links": {
    "self": {
      "href": "/"
    }  },  "ohai": "IAMA response"}
api.fetch().then(function (root) {  console.log(root.props.ohai); // "IAMA response"
});

CURIES

{
  "_links": {
    ...
    "curies": [
      {
        "name": "ht",
        "href": "http://haltalk.herokuapp.com/rels/{rel}",
        "templated": true
      }
    ],
    "ht:signup": {
      "href": "/signup"
    },
    "ht:me": {
      "href": "/users/{name}",
      "templated": true
}}
root.links['ht:users'] === root.links['http://haltalk.herokuapp.com/rels/users'];

Links

{
  "_links": {
    ...
    "curies": [
      {
        "name": "ht",
        "href": "http://haltalk.herokuapp.com/rels/{rel}",
        "templated": true
      }
    ],
    "ht:signup": {
      "href": "/signup"
    },
    "ht:me": {
      "href": "/users/{name}",
      "templated": true
} }
root.link('ht:me', { name: 'mike' }).fetch().then(function (user) {
  assert(user.props.username === 'mike');
});




Passing on the Hyperagent
Resource means passing on
the application state.


Hyperagent meets AngularJS


<li ng-repeat="user in root.links.users">
<button ng-click="showUser(user)">{{ user.props.name }}</button></li>
$scope.showUser = function (user) {  user.fetch().then(function (user) {    $scope.currentUser = user;  });};


What's missing?


HAL is read-only.
Boo.

But it's extensible!


Hyperagent Forms


bower install hyperagent-forms
Hyperagent.configure('loadHooks', [HyperagentForms.LoadHook]);


How do Forms work?


  • _forms: { href, method, schema }
  • JSONSchema: Shared between Client & Server
  "_forms": {
    "ht:signup": {
      "href": "/signup",
      "method": "POST",
      "schema": {
        "$schema": "http://json-schema.org/draft-04/schema#",
        "description": "Create an account",
        "title": "signup",
        "required": [
          "username",
          "password"
        ],
        "type": "object",        "properties": {
          "username": {
            "minLength": 1,
            "type": "string",
            "description": "The username you want to log in with."
          },
          "password": {
            "type": "string",
            "description": "The password you want to log in with."
          }
        }
      }
    }
  }


Submitting


var api = Hyperagent.Resource('https://api-rw.example.com/');
api.fetch().then(function () {
  var signup = new api.forms['ht:signup']({ username: 'mkelly' });
  signup.data.username = 'overwrite';

  if (signup.validate()) {
    signup.submit();
  } else {
    console.error(signup.errors);
  }
});


What actually happens


POST /signup HTTP/1.1
Host: api-rw.example.com
...
Content-Type: application/json
Content-Size: xxx

{
  "username": "mkelly",
  "password": "ilikehal"
}




Stuff I liked

Server-Side


Dougrain is awesome.

doc = HypermediaDocument(block, None)
url = url_for('.block_resource', page_id=page_id, block_id=block_id)

doc.set_property('id', block_id)
doc.add_link('self', url)
doc.add_link('seo:app', url_for('.app_resource', app_name=app_name))
doc.add_link('seo:page', url_for('.page_resource', page_id=page_id))

doc.set_form('seo:delete_block', url, method='DELETE')
doc.set_form('seo:update_block', url, method='PUT', schema=BLOCK_SCHEMA)

return doc


Schema-less rocks for Prototyping


MongoDB may have its flaws,
being slow at trying out ideas sure
isn't one of them.



Encapsulating your 

application state is
 the best thing ever.


Example


$scope.submit = function submit(blockData) {  // page is a HyperResource  var form = new page.forms['seo:add_block'](blockData);  return form.submit();}.then(function (result) {  // promise chaining is awesome, too.  var resource = result.loadResource();  displayBlock(resource);});


Downsides


  • You still rely on relation names
  • Naming things is hard
  • Sometimes ("/url/" + id) would be easier


Upsides


  • Change-Resilient APIs
  • HAL is compatible with "RESTful" APIs
  • Code becomes a lot more DRY



Thx.

Hypermedia APIs

By Pascal