Meaningful API


It is more and more common to receive requests about APIs development. In the web world, APIs are usually accessed via the HTTP protocol and return data in JSON, XML or even HTML format.

First of all: what do I do with an API, developing a website? I can expose methods for remote access (just think about a mobile application, or an admin amministration panel). Secondly, I can easily load data asyncronously (for example to save / load a forum post without having to refresh the page).

But how should I develop them? Is there a hidden pattern or something like that to correctly build them?

Yes and no (you don't say!). Being a web developer, you'd already have heard about or worked with server-side scripting languages like PHP (or even JavaScript with NodeJS!), and you should be familiar with POST and GET data. With just those two methods of data input, you could create an API which looks like this (with URL rewrite, of course):

:::HTTP
POST /api/article/new
POST /api/article/67/edit
GET /api/article/67/delete

... and so on so forth. You could think that could be enought. And that may be true, if you don't need complex APIs, but on the negative side, the developer who access your API may have to learn tons of URLs to interface his program with your API. And that programmer may be yourself, after creating n-different possible URLs and relative methods. What about this way?

:::HTTP
POST /api/article
PUT /api/article/67
DELETE /api/article/67

... or even (but more cryptic) ...

:::HTTP
POST /api/article
PUT /api/article
DELETE /api/article/67

Isn't it more intuitive? What? Do even PUT and DELETE methods exist? Hell yeah! The HTTP protocol allows you to call web pages with the following (standard) methods:

Note that in PHP there is no $_PUT variable. Instead you can use the php://input data stream.

To sort things up, if we want to get or manipulate the article with ID 67, we'll just need to do the following:

:::HTTP
POST /api/article # to create a new article (and maybe get the ID)
GET /api/article/67 # to get the article
PUT /api/article/67 # to update the article
DELETE /api/article/67 # to delete the article

Just one URL "and a half" to handle an entire article with semantic HTTP calls. Isn't this just clean and simple to remember? You may still be doubtful objecting that you could use just POST to do everything, in the following way:

:::HTTP
POST /api/article/new
POST /api/article/update
POST /api/article/get
POST /api/article/delete

I'd say first of all that you're just ignoring what the HTTP protocol is offering you just for those purposes, and second that you're re-defining something already defined in a standard (the HTTP protocol itself!). Think about an API in a language different from English: the keywords (new, update, get, delete) may be different, forcing the developer to RTFM, because the API is not "rock solid".


Data output

So, our API structure looks amazing, but in which language should I return the result? Well, here is where things become really complicated. Facing the problem of creating an API for the first time, you'd probably don't even ask yourself this question, outputting things directly in XML or JSON. And you should probably do just that. But if your API target is someone who knows a little bit of networking, it would be nice to let the API caller choose.

I usually create APIs that output JSON, JSONP, XML and HTML with little to no effort but the HTML part (see the last paragraph about this).

The first approach to "select" the result's language would be to append it somewhere in the URL or in the data fields. But that's not stylish enought for us!

HTTP provides an interesting, linear and logic solution to our problem: the use of the Accept field of the HTTP request's header part. The header is defined as following:

:::HTTP
Accept: media-range[; accept-params]

The Accept header tells the web server in which language the client would like to receive the response.

media-range is the MIME type. The asterisk allows you to not specify a particular kind of MIME type, for example */* means "I accept everything", while audio/* means "I accept any audio format" and application/json means "I accept JSON".

accept-params are optional and tell the server the option(s) about the specified media-range. The only option we need is "q" and stands for "quality factor". This is a value between 0 and 1 (default: 1) which tells the server how much the MIME type is wanted.

Example:

:::HTTP
Accept: application/json; q=1
# is equal to
Accept: application/json

Obviously, the quality factor is useless here (as shown in the second part of the example).

We could even request the server a page allowing different languages, if the preferred one is not available:

:::HTTP
Accept: application/json, application/xml; q=0.5, application/EDI-X12; q=0.1

This means "Hello server, I'd like to get the response in JSON (I really like it!). If you can't, please send me the XML version (I'm not very happy). If you still can't do that, send me the response in EDI-X12, but I'd be very unhappy about that".

Gotcha?

In this way you could elaborate the request in a PHP class, returning a "raw" array, encoded in the right way reading the Accept HTTP header field: so the browser, which prefers text/html, would receive a formatted HTML web page, while accessing the API with JavaScript or an external program would let it return JSON, XML or whatever format you chose sending the request with the custom HTTP field setup correctly.

- 15th June 2013

> back