Pragmatic
Hypermedia
APIs
A bit of theory first, though
Problem
- Desktop
- Smartphones
- Tablets
- Smart Watches
- Glasses
- Server
- XMLRPC
- SOAP
- RESTful
- Hypermedia?
Hypermedia APIS
- Usage of HTTP to its fullest.
- 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
- Analyze Processes
- Create a Finite State Machine
- Evaluate Hypermedia
- 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/');
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
Hypermedia APIs
- 5,628