Easy to Follow Hypermedia Controls with Ketting
Hi! My name is Evert, and for a while now I've been working on a project
called Ketting, which is a generic Hypermedia/HATEOAS client for
Javascript. I just released version 5.0, and thought it might be interesting
to share the new features that have been added since earlier this year.
These are the highlights.
Support For Siren
Ketting now features support for Siren. This means that the client now
understands this format and can transparently traverse Siren links.
const newRes = await res
.follow('author') // this could be a HTML5 link
.follow('me') // This might be a Siren link
Ketting now has support for HTML, HAL, JSON:API, HTTP Link
headers and Siren links. Collection+JSON is also in the works
and will likely follow soon after in a point release.
Advanced Follow Features
Usually if you want to fetch a collection of things, you might do it using
this API:
const articles = await res
.follow('article-collection')
.followAll('item') // This might be a list of links to each individual article
In HTTP/1.1 style APIs, it was common to actually embed the responses of
every 'item' in a collection using for example's HAL _embedded
property or
JSON:API's included
.
This is kind of an anti-pattern, but a necessary evil because HTTP/1.1
requests have a large amount of overhead.
Ketting will take all the items in _embedded
and store the items in
its cache, so that future GET
requests are not needed.
In HTTP/2 APIs requests are less expensive, and it's often desirable to make
more smaller HTTP requests and responses.
To help with this, the follow/followAll have these new features:
Prefetching
const newRes = await res
.follow('article-collection')
.followAll('item')
.preFetch();
The preFetch()
on followAll will cause Ketting to follow every item
link,
do a GET
request for each in parallel in the background and store it in its
cache.
We did some testing with an internal application and were able to prefetch an
entire collection of 1700 items (so 1700 GET
requests) in about 7 seconds.
While this could benefit from some paging, it was great to see how fast HTTP/2
servers can be.
Sending Prefer-Push
Very similarly, there's also a chainable preferPush()
on follow
/
followAll
:
const newRes = await res
.follow('article-collection')
.followAll('item')
.preferPush();
This will cause Ketting to send a Prefer-Push
header when fetching the
articles collection:
GET /articles HTTP/2
Prefer-Push: item
If a server supports this header, they can optimize future requests by sending
HTTP/2 pushes for each linked item.
Support For rel="invalidates"
Ketting now understands the 'invalidates' link relationship from
draft-nottingham-linked-cache-inv.
A use-case for this is that a client might do a POST
request on some
resource, and the result of this resource causes other cached responds
to invalidate.
This is handy, because an operation on one resource can alter the state of
other, unrelated resources. Using this HTTP header, a server can tell a
client which resources those are and control invalidation.
New OAuth2 Library
Older versions relied on the client-oauth2 for everything OAuth2, but this
library is pretty bulky when WebPacked. This has since been changed over to
fetch-mw-oauth2, which is a library that wraps fetch()
as a middleware
and decorates it with OAuth2 features.
This caused the final Ketting Webpack distribution to drop over 30KB. While
making this change, support for OAuth2 authorization_code
was also added.
Per-Domain Authentication
One of the nice advantages of using a hypermedia-style API, is that if multiple
APIs support links, one way to integrate these APIs is simply by pointing links
from one API to another.
A real life example was that one of our APIs needed some integration with the
Github REST api, which uses HTTP Link headers for some stuff.
Our API could simply point to Github endpoints and the client was able to just
traverse the graph and didn't have to be aware that some endpoints were served
by Github.
However, our API and Github's each have their own authentication mechanisms.
In previous Ketting versions authentication could only be set up once globally,
causing the same credentials to be sent everywhere.
With the new authentication layer, it's possible to set Ketting up to use
different authentication mechanisms based on the specific domains you're
accessing, including wildcards.
const ketting = new Ketting(bookmark, {
match: {
'*.github.com': {
auth: {type: 'oauth2', /*...*/ }
},
'api.example': {
auth: {type: 'basic', /*...*/ }
},
}
});
Typescript Improvements
It's now possible to add Typescript annotations to various Ketting methods.
Consider the following example
type Article = {
title: string,
body: string
};
const itemRes = await res1.follow<Article>('item');
This tells Typescript that the result of this follow()
function is actually
a Resource<Article>
.
If a user later calls:
const body = await itemRes.get();
body will now automatically have the Article
type.
Similarly, item.put()
will also now require a parameter of type Article
.
There's many more examples of this, which you can read on my previous post
on this subject.
201 And 205 Responses On POST
If a user calls .post()
and the response to the HTTP POST request was a
201 Created
and a Location
header, it would automatically return a
new resource.
const newArticle = await articlesCollection.post(newArticle);
Now, when a server returns a HTTP 205 Reset Content
, the post()
function will return the resource itself (this
).
This is a small change that I hope is useful for APIs with strong hypermedia
controls and folks that want to create clients that heavily rely on the entire
application state being managed by the server.
Conclusion
I hope you like the changes. My goal is to create the ultimate client for
Hypermedia APIs, and I feel these are some good steps in that direction.
If you're an existing user, I would be very curious to hear what's currently
painful and help adjust the future of this project.
If you're a new user and interested in using it, check out the
Github project and documentation (The latter of which also got a
big overhaul).