Monday, January 12, 2009

Websites are also RESTFul Web Services

I have been reading the Richardson and Ruby book RESTful Web Services and recently had an epiphany: if you design a RESTful web site it is also a RESTful web API. In this post I'll show exactly how that works and how you can use this to rapidly build a prototype of a modern web application.

First of all, let's start with a very simple application concept and build it from the ground up. Let's consider a simple application that lets you keep a list of favorite items. The resources we'll want to model are:

  • a favorite item
  • a list of a user's favorite items

We'll assign the following URLs:

  • /favorites/1234 for favorite item with primary key 1234
  • /favorites is the list of everyone's favorite items
  • /favorites/-/{owner}joe@example.com (or its URL-encoded equivalent) is the list of items belonging to our friend Joe (this is a URL format inspired by Google's GData protocol).

Now, we'll support the following HTTP methods:

  • You can GET and DELETE an individual item (we could allow PUT if we wanted to allow editing, but we'll keep the example simple)
  • You can create a new favorite item with a POST to /favorites.
  • You can GET the list of a user's favorites.

Ok. Now we want to rapidly prototype this so we know if we have the resources modelled correctly. Fire up your favorite web application framework (Ruby on Rails, Django, Spring, etc.) and map those URLs to controllers. Now most of these frameworks let you fill in implementations for the various HTTP methods. We'll make a minor simplification and allow "overloaded POST" where we allow passing a URL parameter to POST to specify PUT and DELETE (e.g. "_method=DELETE"). We can implement the proper HTTP method but we'll allow you to use POST to do it too; browsers and some Javascript HTTP implementations can only do GET and POST.

Ok, now a funny thing happens: it you render an HTML response for everything, you can start playing with your API in your browser! In particular, when we render the list of items, we will naturally put the text of those items on the page, but we can also throw the following HTML snippet at the top of the page:

<p>Add a new favorite:</p>
<form action="/items" method="post">
  <input type="text" name="itemname"/>
  <input type="submit" value="Add"/>
</form>

We can also add the following form after each item's text:

<!-- use a specific item's URL for the action -->
<form action="/items/1234" method="post"/>
 <input type="hidden" name="_method" value="DELETE"/>
 <input type="submit" value="Delete"/>
</form>

which gives us this ugly beastie:

A Few of My Favorite Things:
Add a new favorite:
  • raindrops on roses
  • whiskers on kittens
  • bright copper kettles
  • warm woolen mittens
  • wild geese that fly with the moon on their wings

Now, one key item is that when you render the result page for adding an item, you can send a 201 (Created) response and say something like "item added", throwing in a link back to the list page. The whole HTML response might be nothing more than:

<html>
  <head></head>
  <body>
  <p>I created your item <a href="/items/2345">here</a>.</p>
  <p>All your items are <a href="/items/-/{owner}joe@example.com">here</a>.</p>
  </body>
</html>

We similarly want to render a confirmation page after a DELETE. This makes for an awkward user experience "add, ok, add, ok,..." but you'll notice that the back and forward buttons on your browser actually work without having to rePOST any exchanges.

[Side note: you could, instead of returning a success page, return a 302 that lands you back on the list page, which maybe gets you closer to what you wanted from a user experience, but this is precisely what will break your browser's back button and make you rePOST.]

Now you also have the interesting property that all the links on your site are safe (without side effects) GETs, and all the buttons are potentially destructive (write operations of one sort or another). I say only "potentially" because you might have a search form with action="get" to do a query, and not all of your POST/PUT/DELETEs will actually change anything.

At any rate, at this point, you have a functionally working website that someone could use, if somewhat awkwardly. Plus, if you have my frontend aesthetic design sensibilities, your users will have the pleasure of suppressing a gag reflex while using your site.

So let's spruce this up a little bit. Now, on the HTML page for the list of favorites, we can apply some Javascript. At a first blush, we can hide the delete buttons until a mouseover, which cleans things up somewhat. But the real magic happens when we attach an AJAX event to the delete buttons. Now the script can actually do the very same POST that the form would have done, and then check the HTTP status code, removing the item text from the DOM on success.

Suddenly, the user never leaves that list page, and we haven't had to change any of the rest of the API -- just the HTML representation of that list page. The AJAX call doesn't care if it gets HTML back (in this case), it just cares about the response code. Now we have the nice AJAXy experience we would expect, but oddly enough you still have a website that will work for people with Javascript disabled.

The last step towards finishing out your API is probably simply to make structured versions of your representations available (e.g. JSON or XML formats like Atom) with an optional parameter like "?format=json". Now all of your client-side functions can call URLs with the appropriate format on them and get well-structured data, and everyone else gets HTML.

Well, I guess that's the second to last step. You probably actually want to apply some graphic design and CSS to your site too...

6 comments:

Mat said...

Rails has been really pushing this model since 2.0. It has a map.resource routing method that wires the rest CRUD to controller actions and even generates view helpers for restful links. And the make_resourceful plug-in can wire it to a DB model in just a few lines. Doesn't cover the category query though (yet).

Mat said...

I was thinking about this some more and question the use of '/items/-/{owner}joe@example.com' vs '/items?owner=joe@example.com'.

The GData category thing I can kind of understand, but why create a new (longer) syntax for what already exists?

Jon Moore said...

@Mat: It doesn't surprise me that there are frameworks that set up the boilerplate for this. If all your resources have Atom representations and support the same HTTP methods, there's going to be a lot of common code. Thanks for the links.

Regarding the query syntax, the GData spec does support the category query via query parameters; see the protocol reference for a little more discussion. In my mind, putting it in the URL signifies this as a different resource (Joe's items, rather than all items). But this is really just a matter of taste I think. The only thing I can think of is that some web caches do not cache requests with query parameters (this is Squid's default behavior, for example) so there might be some practical cachability impact.

digital id said...

I am a newbie in this field and thanks for sharing such an informative blog here i never knew that if we design a RESTful web site it is also a RESTful web API.now i can easily use and implement it in this way

Florian R. said...

Why did you chose a query parameter (format) for specifying the desired representation instead of using HTTPs built in functionality of content negotiation through the Accept and Content-Type headers?

Otherwise, I really like the idea of RESTful websites and thought about it a lot before stumbling over your post. Great primer!

Jon Moore said...

@Florian: If you read some of the discussions from the IETF HTTP Working Group, you'll find that there are many folks that think content negotiation was a bad idea in retrospect. Certainly it makes getting caching harder; your intervening caches must handle content variants correctly, and even then you have to make sure the server sets the Vary header properly. Using a query parameter instead is much more unambiguous.