Marcin's Musings

Random bits of stuff

Building REST APIs With FW/1 2.2

| Comments

FW/1, the awesome and lightweight CFML application framework, has version 2.2 just around the corner (in RC as I write this, may be out by the time you read it). Version 2.2 is mostly a bugfix release, however there are two new features that were of particular interest to me: resource routes, and renderData.

Resource Routes

Resource Routes were contributed by John Berquist for FW/1 2.2. This is actually something I had also proposed a long time ago, I mentioned it in my CF.Objective(ANZ) presentation this time last year, but never got around to implementing. So thanks, John!

Resource Routes allow you to simplify adding REST style routes to your route table. In my presentation I showed a set of routes required to implement a RESTful API for a resource, basically Resource Routes allows you to replace those with a single entry using the magic $RESOURCES route.

renderData

The other cool feature is a renderData function that simplifies the process of returning non-HTML content from a FW/1 controller. Currently returning data in JSON, XML and text is supported. There have been a couple of ways of doing this in the past, using an onMissingView function in Application.cfc or using a special view that would set the content type. renderData makes this a one-liner without any additonal requirements.

Building a REST API

By combining renderData and Resource Routes it makes it easier than ever to build a REST style API with FW/1. I’ve created a simple example project that implements, of course, a TODO list application. It’s not necessarily best practice, but I think it shows how easy it is to provide an API and cosume it. For a “real” app for the front-end code I’d probably use something like Backbone or AngularJS instead, but in the interests of keeping the barrier to understanding the code low I’ve kept it as plain JS (with jQuery).

https://github.com/marcins/fw1-rest-example

RESTful routes

This is grossly simplifying the topic, but essentially the idea of REST is that URLs point to particular resources in your data model, and the HTTP method determines what to do with that resoruce (eg. GET to get data, POST to create a new object, PUT to update, DELETE to delete). There is also the concept of child objects, so for example in the TODO app a list item is referenced through it’s list:

/lists/LISTID/items/ITEMID

The routes in Application.cfc to support this are quite simple:

1
2
3
routes = [{
  "$RESOURCES" = { resources = "lists", nested = "items" }
}]

This requires two controllers, lists and items, and with the default settings will create routes for the following actions in the controller:

  • default (GET without id)
  • show (GET with id)
  • create (POST)
  • update (PUT)
  • destroy (DELETE)

For these methods rc will have available, where relevant, lists_id and items_id (in the items controller).

You can configure which methods are available, and a whole bunch of other stuff, in the route as well. The FW/1 manual goes into detail as to what configuration options are available.

Error handling

The easiest way to “throw” an error back to the Javascript is to use a HTTP response code. renderData allows this with an optional status code parameter, for example:

1
2
3
4
var list = getObjectStore().getObjectById(rc.lists_id);
if (isNull(list)) {
  return fw.renderData("json", {error = "List not found"}, 404);
}

Note that the example JS code doesn’t really handle these errors, mainly because I was lacking spare time and wanted to get this post out.

Accessing through Javascript

If you’re using jQuery there’s not really anything special you have to do here, if your actions are returning JSON using renderData("json", ...) then jQuery’s “intelligent Guess” for datatype will know it’s JSON and de-serialize it. The only trickiness is that there are only AJAX helper methods (eg. $.get) for GET and POST, not PUT and DELETE, so if you want to use those you’ll need to use the slightly more verbose $.ajax instead:

1
2
3
4
5
6
7
$.ajax(Example.baseUrl + '/lists/' + selectedList.id + '/items/' + itemId, {
  type: "PUT",
  data: {complete: !selectedList.items[itemId].complete}
}).done(function (data) {
  selectedList.items[itemId] = data;
  renderList();
});

I think it’s only IE6 and lower that doesn’t support anything but GET/POST from an XMLHttpRequest, so you should be OK in most cases.

Other points of interest

  • the Javascript isn’t super efficient and will re-render the list of lists and list items when anything changes to keep the data in sync.

  • there is no security or authentication – again, keeping it simple, but don’t do this in production :)

  • the sample project includes a basic persistent object store to avoid the need for a database. This is definitely NOT for production use, but seemed like a useful thing to have for demos and works like a mini-Couch or MongoDB sort of thing.

Comments