RESTful JSON

Joe Gregorio

As I pointed out in JSON isn't XML REST doesn't just apply to XML formats and that it's possible to use JSON RESTfully and not just in the old patterns.

Theory is nice, but working code is nicer. So let's walk through building a RESTful service using JSON. In this case we are going to be building a cookbook, modeled as a collection ala the Atom Publishing Protocol.

A Collection is nothing more than an encapsulation of CRUD. That's why the concept is so powerful and you see lots of folks doing non-blog stuff with the APP. In this case we want to have a collection but not use Atom, instead we want to manipulate a list of member resources, whose members we can manipulate with GET/PUT/DELETE and have JSON representations.

  Collection
     href     ->   Member Resource
     href     ->   Member Resource
     href     ->   Member Resource

Let's build a familiar looking table for what our collection looks like:

Resource Method Representation Description
Collection GET JSON Get a list of members in the collection.
Collection POST JSON Create a new member of the collection.
Member GET JSON Get a member.
Member PUT JSON Update a member.
Member DELETE n/a Delete a member.

We can leave the actual form on the member representations up to each implementation, but we should give a standard form for the collection document. We could just use an array of URIs:

[
  "http://example.org/coll/1",
  "http://example.org/coll/2",
  "http://example.org/coll/3",
  ...
  "http://example.org/coll/N",
]

But that causes two problems. The first is that the collection could be huge and you might not even want to get all the URIs. We need the equivalent of the Atom Publishing Protocol's link relation of "next" and some place to store it. The second problem is extensibility, there's no place to store my own extensions in the collection document.

So let's take our list and put it in an object, giving us space for our "next" link:

{ 
  "members":
  [
    "http://example.org/coll/1",
    "http://example.org/coll/2",
    "http://example.org/coll/3",
    ...
    "http://example.org/coll/N",
  ],
  "next": "http://example/coll/page/2"
}

The only other thing we need is some place for extensiblity in the list of members, so let's turn each of those into objects also:

{ 
  "members":
  [
    { "href": "http://example.org/coll/1" },
    { "href": "http://example.org/coll/2" },
    { "href": "http://example.org/coll/3" },
      ....
    { "href": "http://example.org/coll/N" },
  ],
  "next": "http://example/coll?page=2"
}

We can also reduce some verbosity if we allow relative URIs in "href". For example, if the URI of this collection document is http://example.org/coll/ then we get:

{ 
    "members":
    [
      { "href": "1" },
      { "href": "2" },
      { "href": "3" },
        ....
      { "href": "N" },
    ],
    "next": "http://example/coll?page=2"
  }

That looks good. Now what would this look like on the wire? Let's say we wanted expose an interface to our cookbook. The cookbook will be a collection and each member will be a recipe. We'll take a very simplistic representation for our recipe for now:

{
  "title": "the name of the recipe",
  "instruction": "the recipe instructions"
}

Here is an example GET to the collection:

GET /coll/ HTTP/1.1
Host: example.org
User-Agent: NotGiven/1.0
Authorization: Basic ZGFmZnk6c2VjZXJldA==
Accept: application/json

And the response may be:

HTTP/1.1 200 Ok
Date: Wed, 20 Sep 2006 17:17:11 GMT
Content-Length: nnn
Content-Type: application/json

{  
  "members": 
  [
    {"href": "chicken"}, 
    {"href": "ribs"}, 
    {"href": "burger"}, 
    {"href": "pizza"}
  ],
  "next": null
}

Now to retrieve the first member of the collection:

GET /coll/chicken HTTP/1.1
Host: example.org
User-Agent: NotGiven/1.0
Authorization: Basic ZGFmZnk6c2VjZXJldA==
Accept: application/json

And the response may be:

HTTP/1.1 200 Ok
Date: Wed, 20 Sep 2006 17:18:11 GMT
Content-Length: nnn
Content-Type: application/json

{
    "title": "Southern Fried Chicken", 
    "instruction": "First get a chicken..."
}

To update our recipe we PUT an updated JSON representation back the same URI:

PUT /coll/chicken HTTP/1.1
Host: example.org
User-Agent: NotGiven/1.0
Authorization: Basic ZGFmZnk6c2VjZXJldA==
Content-length: nnn 
Content-type: application/json

{
  "title": "Southern Fried Chicken", 
  "instruction": "First gather the ingredients: 
    a whole chicken, 
    one quart of buttermilk..."
}

And the response may be:

HTTP/1.1 200 Ok
Date: Wed, 20 Sep 2006 17:18:12 GMT
Content-Length: 0 

DELETE is used on the member to remove it from the collection, and we POST a JSON representation to the URI of the collection to create a new entry:

POST /coll HTTP/1.1
Host: example.org
User-Agent: anonymous/2.0
Authorization: Basic ZGFmZnk6c2VjZXJldA==
Content-Type: application/json
Content-Length: nnn

{
  "title": "Trout on a stick",
  "instruction": "First get a trout and a stick..."
}

The server signals a successful creation with a status code of 201 and the response includes a "Location" header indicating the URI of the newly created member.

HTTP/1.1 201 Created
Date: Fri, 7 Oct 2006 17:17:11 GMT
Content-Length: nnn
Content-Type: application/json
Location: http://example.org/coll/trout

That seems like a good start, next we'll build a server-side implementation.

comments powered by Disqus