Skip to content

On REST

Like the word "service", the word "REST" is now so overloaded that it has become entirely decoupled from its original meaning. In fact, there's a good argument to be made that the set of techniques that we now call "REST" is in fact the exact opposite of what REST was originally supposed to mean. Therefore there is little point in dwelling on the original meaning or arguing about whether something is or is not truly RESTful. Let's just get on with building stuff.

REST is built on top of HTTP, and HTTP is just bytes being sent from one computer to another. The two computers just have to agree on the format and structure of the bytes for HTTP to be a useful way to communicate. The humans operating each computer have to agree on the meaning of the data within that structure for it to be a practical building block for system design. That's all. If the most useful way to design your system is to religiously follow a Roy Fielding-esque HATEOAS approach, go for it. If instead your goal is to get data from point A to point B in the most straightforward way possible, you might choose to do things differently.

Methods

Quote

The request method token is the primary source of request semantics; it indicates the purpose for which the client has made this request and what is expected by the client as a successful result.

HTTP spec

The authors of the early web browsers (pre-JavaScript) decided that there is a useful distinction to be drawn between requesting data from a server (using the GET method) and mutating state on the server (using the POST method). Originally, these were the only two methods supported.

Just like how we distinguish between "readers" and "actions" when structuring our business logic, drawing a conceptual line between retrieving and storing data at the HTTP level is useful. It provides two straightforward mental "buckets" in which to put our patterns and practices. It allows us to align our approach to views (discussed soon) with our approach to business logic (readers/actions) with our outward-facing API.

Later, the HTTP/1.1 spec promoted a wider set of methods, including PUT and DELETE. Here, I think, is where the semantics started to leak a little: it's not always clear from the definitions of these methods in the spec exactly how their behaviour should be interpreted. For example, consider DELETE: in a system which implements "soft deletes" (where a conceptual delete is in fact implemented as an update), should DELETE still be used?1

The PATCH method, specified in a separate RFC, is even muddier: it specifies that a request "contains a set of instructions describing how a resource currently residing on the origin server should be modified to produce a new version" without specifying anything about the format of the patch document and how it should be interpreted. Anecdotally, this latter fact doesn't seem to be widely known amongst developers: I've spoken to people who are convinced that the specification for the PATCH method describes exactly how a resource should be patched, to the extent that they didn't even realise that the question could or should be asked. In fact, deciding on how to patch a resource is entirely left as a problem for the developer to solve.

I would argue that the methods GET and POST are categorically different to other methods such as PUT, DELETE and PATCH. The former are a universally-applicable distinction between whether or not a particular operation conceptually changes server state. The latter are beginning to move more in the direction of talking about the specific vocabulary of things that a particular application can do, but are of course far too restrictive to comprehensively serve this purpose. This attempt to apply a very limited set of verbs to a very wide set of possible actions tends to create much confusion in developers who aren't sure which method to use, or disagree with other developers on the meaning of those methods.

To summarise: there's a huge benefit, in terms of both cognitive load and complexity of implementation, to restricting the design of your applications so that they only respond to GET or POST requests2. This applies both to applications that return HTML to the browser (in this case, GET and POST are the only methods that can be supported), and also to applications that expose a machine-to-machine API, probably using JSON request and response bodies.

URLs

If we aren't using the HTTP method to represent the "verb" (the thing we actually want to do as a result of an HTTP request), where should this information go? Again, "REST" purists may want to hold their noses: it should go in the URL. The simplest way to think of a URL is as an identifier of a particular behaviour of your system. If that behaviour happens to be retrieving a representation of some data, then it makes sense to use the traditional "URL identifies a resource" framing. Here, the generic verb (GET) aligns perfectly with the specific system behaviour. If the behaviour is an action, on the other hand, then it's far simpler to admit that putting the verb in the URL is the simplest way to identify that action. The "conceptually pure" strategy of somehow conceptually converting the action (verb) into a resource (noun) and applying the limited set of HTTP verbs is performative busywork.

The final point to make in this section is specific to HTTP APIs (rather than HTML-on-the-server applications) and is more practical than philosophical, but still worth pointing out. Django REST framework, and many other API frameworks, are designed around this concept of "resources" and "methods" on them. For this reason, it's possible, and encouraged by the documentation, to mount a view at a particular path (identifying the "resource"), and then vary the behaviour depending on the HTTP method "verb" (retrieve, update, delete). In my experience, this approach ends up making the code excessively complex and difficult to follow, because the view class ends up overloaded with far too much behaviour, usually expressed through a towering hierarchy of mixin inheritance. It's far better, instead, to say that any given URL maps to a single view and only responds to a single method (GET or POST, but not both). This makes both the code and the API surface far easier to understand.

If you're thinking that the advice in this section describes something closer to an "RPC-over-HTTP" API, then you'd be right. An HTTP API, after all, is intended to expose the functionality of your server-side application to a client, whether that's a client you control (like a single-page web application or a mobile app) or a client you don't (in the case of a public-facing API). Your job as an API developer is to expose this functionality in the most straightforward way possible, both from the point of view of the internal implementation that you maintain, and the concepts that you provide to clients. I would argue that both of those goals are best served by creating an interface that closely maps onto the actual code and concepts that exist on the server. The client uses the HTTP method to tell the server whether it wants to retrieve some data (GET) or perform some action (POST). It uses the URL to identify which data to retrieve, or which action to take. Conceptually simple and practically straightforward.

Summary

Developers building "REST APIs" often get too hung up on dogma and misunderstandings about what REST actually means. By focusing on practicality over purity, we can define a very straightforward set of guidelines that make our APIs both easy to implement and maintain, and easy for a consumer to understand. A single URL maps to a single behaviour (a "view" in Django), and a single view responds only to GET or POST, not both.


  1. If you feel you have a confident answer to this question, I can guarantee that someone else has an equally confident answer that is the opposite of yours. 

  2. I'm not mentioning HEAD and OPTIONS because they can be automatically inferred by the framework from the structure of the code.