REST stands for representational state transfer, and is a set of architectural styles that dictates the manners and patterns in which you construct your API. REST is nothing new; you are probably already well attuned to it because that's how the World Wide Web is structured, so don't let the terminology alienate you.
There are six requirements for REST:
- Client-server: Defines a clear separation of concerns (SoC) between client and server. The client should provide the user interface, while the server provides the data.
- Stateless: No transient information about the client should be held on the server. In other words, the server should not persist client sessions; if sessions need to be persisted, it must be done on the client. Any requests that arrive at the server must contain all the information required to process that request.
This is not to say that servers cannot store any state; servers can still persist resource state inside databases. But servers should not store temporary application state in memory.
The importance of this constraint will become apparent in Chapter 18, Robust Infrastructure with Kubernetes, when we deploy our application as a cluster of load-balanced servers. By being stateless, requests can be fulfilled by any of the servers in the cluster, and servers can be restarted without losing information. It is this constraint that allows for the scalability of our application.
However, this constraint does have its drawbacks, because the client must repeatedly send authentication information (for instance, a JSON Web Token, or JWT) with each request, increase the bandwidth used. - Cacheable: If a response is going to be the same given the same request, then that response should be cached by the client and/or any intermediaries. A RESTful architecture requires that the response message must include an indication of whether the response should be cached, or not, and if so, for how long.
This constraint could be beneficial as it helps reduce bandwidth usage, and can reduce the load on the server, freeing it up to service more requests. - Layered system: Many applications, especially Node.js applications, are reverse proxied by a web server (for instance, NGINX). This means that before a request reaches our application, it may pass through layers consisting of web server(s), load balancers (for instance, HAProxy), and/or a caching server (for instance, Varnish).
The layered system constraint dictates that the client should not know about these layers; in simpler terms, the client should not have to care about the implementation of the server. - Code on demand: An optional constraint that allows the server to return code for the client to execute. For example, the server may send back custom JavaScript code, Java applets, or Flash applications. This can be viewed as an extension to the client-server constraint, as it ensures that the client doesn't need to implement code specific for that server, which would otherwise couple the client and server together.
- Uniform interface: An interface is a shared boundary that is used to exchange information between two components. An interface is important as it decouples the server from the clients; as long as both adhere to the same interface, they can be developed independently.
The uniform interface constraint specifies rules on how this interface should be structured, and is further subdivided into four sub-constraints (a.k.a. interface constraints):- Identification of resources: a unit of data that is stored on the server is called a resource. The resource is an abstract entity, such as a person or a product. This constraint requires that our API assign an identifier to every resource. Otherwise, the client won't be able to interact with it.
When using REST with HTTP, this constraint is fulfilled by the use of Uniform Resource Locators, or URLs. For example, product #58 should be accessible through the URL api.myapp.com/users/58/. - Manipulation of resources through representations: You can represent a resource in different formats, such as XML or JSON. These are different representations of the same resource.
If a client wishes to manipulate a resource in some way, this constraint requires the client to send a full or partial representation of the desired state of the resource.
As an extension to this, the server should also indicate to the client which representations it is willing to accept, and which representation it is sending back. When using REST with HTTP, this is done through the Accept and Content-Type headers, respectively. - Self-descriptive messages: The response from the server should contain all the information the client requires to process it properly.
- Hypermedia as the engine of application state (HATEOAS): This requires the server response to include a list of actions that the client can take after receiving the response.
- Identification of resources: a unit of data that is stored on the server is called a resource. The resource is an abstract entity, such as a person or a product. This constraint requires that our API assign an identifier to every resource. Otherwise, the client won't be able to interact with it.
In order to apply the "RESTful" label to an API, it must adhere to all the constraints except code on demand (which is optional).