I’ve been building REST based web services for years and they’ve formed the mainstay of APIs for most of the applications I’ve worked on, including business-to-business systems, mobile applications and for web sites using AJAX to call the RESTful services.
Now though I’m working on a web application that aims to be resource oriented rather than, for example, a single-page web application. One of the goals to be that the web site itself is its own RESTful API.
When I create a RESTful service to be used as an API, I make use of the standard HTTP status codes together with the full range of HTTP methods.
For example, a simple CRUD application to manage ‘orders’ makes use of the following URIs, and methods:
GET /orders – returns a list of orders (using query parameters to handle paging)
GET /orders/new – returns a page including a form to allow creation of a new order (the form POSTS to):
POST /orders – creates a new order
GET /orders/1 – gets a specific order
GET /orders/1/edit – returns a page including a form to allow editing of the existing order (the form PUTS to):
PUT /orders/1 – updates the specific order.
DELETE /orders/1 – deletes the specific order.
When creating a new order by POSTing to /orders, in an API style RESTful web service, I would return a “201 Created” status code and set the Location header to the URI of the new order’s resource. The client can then decide whether to GET the new order’s resource or do something else such as POSTing to create another new order.
Now I come to building my RESTful web site that uses the browser as the client and HTML as the media type. I could do as I would normally and return “201 Created” when the user submits their form, which POSTs to the /orders resource.
The HTTP specification says that the returned entity should “contain a list of resource characteristics and location(s) from which the user or user agent can choose the one most appropriate”. In this case the entity I return would be a page representing the list of orders which includes the order that was created – i.e. the same as returned when GETting /orders.
Unfortunately, browsers have a refresh or reload feature, which simply resubmits the previous request and if that was a POST it resubmits the POST. Since POST is not idempotent, it creates another new order.
To solve this problem modern web sites implement the POST-redirect-GET pattern: when processing a POST, the server returns a “303 See Other” response that tells the client to GET another resource. In the Orders application this resource could be the /orders resource to get the list of orders.
To the end user the flow looks the same. They submit their form to create a new order and they see the list of orders including their new order.
With the POST-redirect-GET pattern though, when the user refreshes the browser, it doesn't create another order.
So what should the application do? To be a good RESTful API, the POST should return a “201 Created”. To be a good web site usable in a browser, the POST should return a “303 See Other”
There is an alternative though, albeit a one which isn't part of the standard, but is a defacto standard.
The solution is to use the "201 Created" status code, and to send a "Refresh" header to inform the browser to load another resource, the resource that would be returned with the "303 See Other" response of the POST-redirect-GET pattern.
This way the web application can continue to work as both a web site, with support for the browser's refresh button, and as an API for programmatic clients.