Now that we've covered the main REST principles, it is time to dive deeper into what can be achieved when they are followed:
- Separation of the representation and the resource
- Visibility
- Reliability
- Scalability
- Performance
Now that we've covered the main REST principles, it is time to dive deeper into what can be achieved when they are followed:
A resource is just a set of information, and as defined by principle 4, it can have multiple representations; however, its state is atomic. It is up to the caller to specify the desired media type with the Accept header in the HTTP request, and then it is up to the server application to handle the representation accordingly, returning the appropriate content type of the resource together with a relevant HTTP status code:
Let's assume that, at server side, we have items resources stored in an XML format. We can have an API that allows a consumer to request the item resources in various formats, such as application/xml, application/json, application/zip, application/octet-stream, and so on.
It would be up to the API itself to load the requested resource, transform it into the requested type (for example, JSON or XML), and either use ZIP to compress it or directly flush it to the HTTP response output.
The caller would make use of the Accept HTTP header to specify the media type of the response they expect. So, if we want to request our item data inserted in the previous section in XML format, the following request should be executed:
GET /category/watches/watch-abc HTTP/1.1 Host: my-computer-hostname Accept: text/xml HTTP/1.1 200 OK Content-Type: text/xml <?xml version="1.0" encoding="utf-8"?> <Item category="watch">
<Brand>...</Brand>
</Price></Price> </Item>
To request the same item in JSON format, the Accept header needs to be set to application/json:
GET /categoery/watches/watch-abc HTTP/1.1 Host: my-computer-hostname Accept: application/json HTTP/1.1 200 OK Content-Type: application/json { "watch": { "id": ""watch-abc"", "brand": "...", "price": { "-currency": "EUR", "#text": "100" } } }
REST is designed to be visible and simple. Visibility of the service means that every aspect of it should self-descriptive and follow the natural HTTP language according to principles 3, 4, and 5.
Visibility in the context of the outer world would mean that monitoring applications would be interested only in the HTTP communication between the REST service and the caller. Since the requests and responses are stateless and atomic, nothing more is needed to flow the behavior of the application and to understand whether anything has gone wrong.
Before talking about reliability, we need to define which HTTP methods are safe and which are idempotent in the REST context. So, let's first define what safe and idempotent methods are:
The following table lists which HTTP methods are safe and which are idempotent:
HTTP method | Safe | Idempotent |
GET | Yes | Yes |
POST | No | No |
PUT | No | Yes |
DELETE | No | Yes |
Consumers should consider operation's safety and the idempotence features in order to be served reliably.
So far, we stressed the importance of having stateless behavior for a RESTful web application. The World Wide Web (WWW) is an enormous universe, containing huge amount of data and a lot of users, eager to get that data. The evolution of the WWW has brought the requirement that applications should scale easily as their load increases. Scaling applications that have a state is difficult to achieve, especially when zero or close-to-zero operational downtime is expected.
That's why staying stateless is crucial for any application that needs to scale. In the best-case scenario, scaling your application would require you to put another piece of hardware for a load balancer, or bring another instance in your cloud environment. There would be no need for the different nodes to sync between each other, as they should not care about the state at all. Scalability is all about serving all your clients in an acceptable amount of time. Its main idea is to keep your application running and to prevent Denial of Service (DoS) caused by a huge amount of incoming requests.
Scalability should not be confused with the performance of an application. Performance is measured by the time needed for a single request to be processed, not by the total number of requests that the application can handle. The asynchronous non-blocking architecture and event-driven design of Node.js make it a logical choice for implementing an application that scales and performs well.