Using the REST interface as the JavaScript interface with Fermata

In support of an upcoming &yet product (ssssssh!), I was asked to create a JavaScript wrapper around a REST-based API we're using from node.js.

If you've been there, you might know how it goes: guess which API features the current project actually needs, make up some sort of "native" object representation, implement some bridge code that kinda works, and as a finishing touch, slap a link to the service's real documentation atop the code you left stubbed out for later.

Or, you find someone else's wrapper library. They took the time to implement most features, and even wrote their own version of the documentation — but the project they needed it for was cancelled years ago, so their native library still wraps the previous version of the server API, without the new features you need.

FACTS

On one hand, the HTTP REST server offers all the newest features, with official usage documented by the service provider. On the other hand, your JavaScript code should be fluently written, following the native programming language idioms. Can we keep it that way?

It's a paradox, really:

  • the REST interface is the BEST interface.

  • the best interface is a native interface.

Do you see where this is going? It took me a while, but after wrestling with the design of yet another web service wrapper, I finally saw the whole coin that I'd been flipping. It's called Fermata, because when I finally put the two sides together I was working from a cheerful Italian caffè and needed a lively but REST-ful word.

In REST you have nouns and verbs — resources and methods — URLs and GET/PUT/POSTs. In JavaScript you have objects and methods — nouns and verbs. So in Fermata, URLs are objects, and methods are, well...methods on those objects:

var rest_server = fermata.api({url:"http://couchdb.example.com:5984"});
var my_document = rest_server.mydata.sample_doc;
my_document.put({title:"Fermata blog post", content:"?"}, function (err, response) { if (!err) console.log("Relax, your data is in good hands."); });

Hey, presto, abracadabra! Pretty simple, eh?

So...is it magic?

Yes, it is magic.

Explain this sleightly hand. Or die.

Easy there, fair Internet reader person! Your dollar was just hiding right there in your ear.

To make the dot syntax work without having to know all the paths available on the server, Fermata uses a feature of an upcoming JavaScript Harmony proposal called catch-all proxies. Proxy-fied objects finally give JavaScript developers a way to intercept all access to an object or function, injecting custom behaviour that would otherwise be impossible.

ECMAScript 5 (the latest JavaScript standard; you can tell it is Web Standard since it ends in 5) let us define property descriptors to handle the actual fetching and storing of pre-declared object keys — that is, you could have custom behaviour for specific properties only if their names were known beforehand:

var myObject = {}
Object.defineProperty(myObject, 'someSpecificProperty', {get: function () { return "someSpecificProperty has this value"; }});
myObject.someSpecificProperty === "someSpecificProperty has this value";
myObject.someOtherProperty === undefined;

ECMAScript Harmony (an in-progress proposal for the next version of JavaScript) wants to take this a step further: rather than just controlling a few pre-defined properties of an object, you can control access to any key — the property's keyname is handed to a completely generic "get" function trap on the object:

var myObject = Proxy.create({get: function (obj, keyName) { return keyName + " has this value"; }}, {});
myObject.someSpecificProperty === "someSpecificProperty has this value";
myObject.someOtherProperty === "someOtherProperty has this value";

So the subpath keys of a Fermata URL don't actually exist. (I told you it was magic.) Instead, when you assign var obj = url.path the JavaScript engine calls a proxy "trap" handler function, that Fermata provides, instead: "hey, for key named path on the object url, what should I say the value is?". Fermata says: "I'll make a new, slightly longer, URL proxy" and so that's what the JavaScript engine assigns to var obj. If you then access a property on obj, Fermata just returns yet another object created via Proxy. Smoke and mirrors.

Of course, where there's smoke and mirrors there must be fire and medicine cabinets. I said ECMAScript Harmony is a proposal that "wants to" standardize Proxy objects in JavaScript — a future version of JavaScript. Fortunately for us impatient types, an intrepid developer named Sam Shull has stocked the node.js medicine cabinet with node-proxy.

While it differs a little from the official Harmony proposal, his V8 Proxy library made Fermata possible. Made Fermata magic.

dramatic pause

But my web browser isn't magic, yet

Firefox 4's JavaScript engine implements the new Proxy object feature, but to reliably use Fermata's magic on the web we'd have to wait for broader support. (Chrome might be next; the race is on, fellas!) In the meantime, I've designed Fermata so that anything you can do with dots and brackets you can do with parentheses, and more!

var homebase = fermata.api({url:"", user:"webapp", password:SESSION_ID});
var latestMessages = homebase('api')('user')('messages.json');
latestMessages() === "/api/user/messages.json";    // use empty parens for the URL as a string
latestMessages.get(function (e, messages) { console.log(messages); });

You can also use the parenthesis syntax to pass an array, which is how you prevent the automatic URL component escaping Fermata does normally. Starting from the CouchDB restServer example above:

recent_docs = rest_server('mydata')(['_design/app/_view/by_date'], {reduce:false, descending:true, limit:10});  // keep a view query handy
recent_docs() === "http://couchdb.example.com:5984/mydata/_design/app/_view/by_date?reduce=false&descending=true&limit=10";
recent_docs.get(...you know the drill...);

Volunteer from the audience

I'd encourage you to give Fermata a spin the next time you only want magic cutting between you and your favorite REST service. It's hosted on github and installable via npm, under the terms of your friendly local MIT License.

After writing and using various REST wrapper interfaces through the years, I'm excited that I can finally speak both fluent HTTP and native JavaScript at the same time. In the office next door, Henrik is already using it from node.js to access several REST service APIs via the one consistent interface Fermata provides. As web applications move more code to the client, and more services implement careful CORS support,

Fermata can provide a high-level AJAX microframework in the browser too.

One next step for Fermata is to add plug-in support for taking care things like of default URLs, setting required headers, converting from XML instead of JSON, and signing OAuth access. The idea is not to wrap the wrapper. More like a musical key signature: do some initial site-specific setup, and the plugin will take care of any API-specific themes while the rest of your JavaScript notation is consistent. Something along the lines of:

var twitter_client = fermata.api({twitter:CLIENT_KEY, user:ID, solemn_developer_promise:"I accept and do acknowledge Tweetie's forever victory, it was a fantastic app while earning its overlord status."});
twitter_client.statuses.user_timeline.get(...);    // same ol' Fermata, but plugin is handling OAuth and format stuff

...maybe? Feedback on the plug-in interface, and anything really, is always appreciated!

You might also enjoy reading: