A RESTful Hypermedia API in Three Easy Steps
2010-04-07
addedrel="{link-relation}"
to the second example. when the link element name (query
) is ambiguous, link relations can be used to add semantic meaning to the hypermedia link.thanks to a number of people who pointed out this omission in my original post (mca)
over the last few weeks i've been ranting about (un)RESTful APIs. most of my complaints focus on the URI-centric nature of the latest round of Web APIs. instead of digging up the same bushwa all over again, i decided to do something else.
i'll design a RESTful hypermedia API in three easy steps. and i'll do my best to follow all the rules. let's see how i do, eh?
A List Management Service
i'll keep it simple. this API will define a simple list management service. you can use it to add, edit, and delete items from a list like a "to-do" list, a shopping list, etc. you can also execute a few simple queries against the list including all the open items, all the items due today, or items due within a date range.
if i was coding a local application, i might use a set of function signatures that look like this:
GetList() GetItem(id) AddItem(name, description, date-due, completed) UpdateItem(id, name, description, date-due, completed) DeleteItem(id) GetOpenItems() GetTodaysItems() GetItemsByDate(date-start,date-stop)
ok, that's enough preamble. let's design a RESTful Hypermedia API!
Step One - Data Structure
i need to design a data structure that allows me to express the contents of the list. i don't want to just send data, i want to also include descriptive information - the "what" of the data; the structure. i'll use XML as my data format today. it's a bit verbose, but it will do and i'm in a hurry.
due to my service's simplicity, i only need three XML elements:
list
(the root element), item
(a single to-do item),
and data
(a data element of an item).
oh, let's also add one more element: query
(i'll use that to define query templates).
here's a simple table detailing all four elements, whether they are required, their attributes, and any possible child elements.
Name | Appearance | Attributes | Children |
---|---|---|---|
list | MUST | href="{collection-uri}" | item (0..+) |
item | MAY | href="{item-uri}" | data (1..+) |
data | MAY | name="{data-name}" | none |
query | MAY | href="{query-uri}" | data (0..+) |
that's all there is to it for the data structure. here's a sample implementation of the data format.
i left out the query
elements an all the href attributes for now.
<?xml version="1.0" encoding="utf-8"?> <list> <item> <data name="title">First Task</data> <data name="description">Produce first draft of Task media-type</data> <data name="date-due">2010-03-21</data> <data name="completed">false</data> </item> <item> <data name="title">Second Task</data> <data name="description">Implement REST version of Task Service over HTTP</data> <data name="date-due">2010-03-22</data> <data name="completed">false</data> </item> <item> <data name="title">Next</data> <data name="description">Go fishing</data> <data name="date-due">2010-03-23</data> <data name="completed">false</data> </item> </list>
Step Two - Hypermedia Semantics
alas, for a useful API, i need more than just a data structure; i also need a way to express the various operations that can be performed on the data. if structure is the way to say what the data means, then semantics is the way to say what can be done with that data. and a RESTful API uses hypermedia links to do that.
again, since this is such a simple service, i only need to define three hypermedia links and
their associated semantics: collection-uri
, item-uri
, and query-uri
.
here are the semantic details for each of these links:
- The <list /> Element and Collection URI
-
Used to retrieve a valid representation with
the full collection of list items. It is also the target URI when adding a new
<item />
to the collection. In the case of add operations, a valid <item />
element must be sent as the message body of the request. - The <item /> Element and Item URI
-
Used to retrieve a valid representation
populated with a single list item. It is also the target URI when updating an existing
<item />
or when removing an item from the collection. In the case of update operations, a valid<item />
element must be sent as the message body of the request. - The <query /> Element and Query URI
-
Used to retrieve valid representation containing a filtered list
of list items. If the
<query />
element contains one or more<data />
child elements, those child elements (their name and associated values) should be added to the URI as query arguments.
by the way, when i combine a data structure definition with associated link semantics, i get a media type. a Hypermedia type, actually. for protocols that like that sort of thing, i'll give my new data+semantics it's own media-type identifier:
application/list.man+xml
i should also register this fancy new media type with the IANA, but i'm kinda busy right now...
ok here's the same data shown earlier, but with the semantic links added. i included some
sample query
elements, too. this version uses placeholders for the links. i'll clear that up in the next step.
<?xml version="1.0" encoding="utf-8"?> <list href="{collection-uri}"> <item href="{item-uri}"> <data name="title">First Task</data> <data name="description">Produce first draft of Task media-type</data> <data name="date-due">2010-03-21</data> <data name="completed">false</data> </item> <item href="{item-uri}"> <data name="title">Second Task</data> <data name="description">Implement REST version of Task Service over HTTP</data> <data name="date-due">2010-03-22</data> <data name="completed">false</data> </item> <item href="{item-uri}"> <data name="title">Next</data> <data name="description">Go fishing</data> <data name="date-due">2010-03-23</data> <data name="completed">false</data> </item> <query href="{query-uri}" rel="today" /> <query href="{query-uri}" rel="open" /> <query href="{query-uri}" rel="date-range" > <data name="date-start"></data> <data name="date-stop"></data> </query> </list>
Step Three - Protocol Implementation
you might have noticed that my media-type definition includes no pre-defined URIs. that's 'cuz the URI values are not important to the definition, just their meaning. the actual values only come into play when i need to come up w/ a protocol-specific implementation of my media type.
of course, the most common application protocol for distributed network apps is HTTP. but it's not the only one. i could also implement this service using FTP. but i'm getting a bit ahead of myself.
implementing the service means i need to match the link semantics with the target application protocol. lucky for me, this service matches up with the HTTP protocol very well:
- Collection URI
-
-
Example:
http://www.example.org/list/
-
HTTP GET {collection-uri}
returns a valid list manager document with multiple existing items. -
HTTP POST {collection-uri}
along with a valid list document containing a single valid<item />
element adds a new item to the list
-
Example:
- Item URI
-
-
Example:
http://www.example.org/list/1
-
HTTP GET {item-uri}
returns a list manager document containing the associated single valid<item />
element -
HTTP PUT (item-uri}
along with a valid task list document containing a single valid<item />
element updates the associated existing item -
HTTP DELETE {item-uri}
removes the associated item from the list
-
Example:
- Query URI
-
-
Example:
http://www.example.org/list/?today
-
Example:
http://www.example.org/list/?open
-
Example:
http://www.example.org/list/?date-start=2010-03-01&date-stop=2010-03-31
-
HTTP GET {query-uri}
returns a list manager document containing zero or more<item />
elements that match the query criteria. -
If the <query /> element in the list manager document has child
<data />
elements, the name and value attributes of those elements should be added to the URI to form a valid query.
-
Example:
yeah, that's pretty easy. it works for FTP, too:
- Collection URI
-
-
Example:
ftp://www.example.org/list/
-
FTP RETR {collection-uri}
returns a list manager document with multiple existing items. -
FTP STOU {collection-uri}
along with a valid list manager document containing a single valid<item />
element adds a new item to the list
-
Example:
- Item URI
-
-
Example:
ftp://www.example.org/list/1
-
FTP RETR {item-uri}
returns a list document containing the associated single valid<item />
element -
FTP STOR (item-uri}
along with a valid list document containing a single valid<item />
element updates the associated existing item -
FTP DELE {item-uri}
removes the associated item from the list
-
Example:
- Query URI
-
-
Example:
ftp://www.example.org/list/today
-
Example:
ftp://www.example.org/list/open
-
Example:
ftp://www.example.org/list/date-start/2010-03-01/date-stop/2010-03-31
-
FTP RETR {query-uri}
returns a list document containing zero or more<item />
elements that match the query criteria. -
If the
<query />
element in the list document has child<data />
elements, the name and value attributes of those elements should be added to the URI to form a valid query.
-
Example:
yes, STOU
is an optional FTP command and my implementation of the query
semantics for FTP is a bit of a stretch, but it's all possible, eh? besides, since the
media-type is protocol-agnostic, servers and clients are free to mix and match protocols;
even in the same representation. for example, some operations might be done using the
HTTP protocol and others could be done using FTP. and any new protocol that comes along
that is a good fit will work, too.
The Bottom Line
so, there you have it. a RESTful Hypermedia API. and it was done without resorting to documenting URI conventions or tying the service to a single application protocol. instead, it was designed using a media type that includes both structure and semantics; data and links. it's easy for a state machine client to implement and it's easy for servers to implement; for a wide range of list-based services (not just to-do lists).
sure, it can be improved quite a bit; by enhancing the media-type definition.
for example, large lists would benefit from a
hypermedia link definition that supports paging
. if the media-type needs to support user display and interaction, richer
form-based hypermedia elements
could be used. maybe the query
elements should be more
XML-like
or possibly be expressed as
URI templates.
the good news is that improving the media-type by adding new elements and semantics can be done safely; without breaking existing clients. without worrying about new URIs or new conventions.
and, you know. it's not all that hard to do. it just takes some thought and planning. so, let's see more RESTful Hypermedia APIs out there, OK?