This article covers two important best practices for REST and RESTful APIs: Naming conventions and API Versioning.
This article is taken from the book Hands-On RESTful Web Services with TypeScript 3 by Biharck Muniz Araújo. This book will guide you in designing and developing RESTful web services with the power of TypeScript 3 and Node.js.
One of the keys to achieving a good RESTful design is naming the HTTP verbs appropriately. It is really important to create understandable resources that allow people to easily discover and use your services. A good resource name implies that the resource is intuitive and clear to use. On the other hand, the usage of HTTP methods that are incompatible with REST patterns creates noise and makes the developer's life harder. In this section, there will be some suggestions for creating clear and good resource URIs.
It is good practice to expose resources as nouns instead of verbs. Essentially, a resource represents a thing, and that is the reason you should use nouns. Verbs refer to actions, which are used to factor HTTP actions.
Three words that describe good resource naming conventions are as follows:
Some example resources are as follows:
Users of a system Blogs posts An article Disciplines in which a student is enrolled Students in which a professor teaches A blog post draft
Each resource that's exposed by any service in a best-case scenario should be exposed by a unique URI that identifies it. It is quite common to see the same resource being exposed by more than one URI, which is definitely not good. It is also good practice to do this when the URI makes sense and describes the resource itself clearly. URIs need to be predictable, which means that they have to be consistent in terms of data structure. In general, this is not a REST required rule, but it enhances the service and/or the API.
A good way to write good RESTful APIs is by writing them while having your consumers in mind. There is no reason to write an API and name it while thinking about the APIs developers rather than its consumers, who will be the people who are actually consuming your resources and API (as the name suggests). Even though the resource now has a good name, which means that it is easier to understand, it is still difficult to understand its boundaries. Imagine that services are not well named; bad naming creates a lot of chaos, such as business rule duplications, bad API usage, and so on.
In addition to this, we will explain naming conventions based on a hypothetical scenario.
Let's imagine that there is a company that manages orders, offers, products, items, customers, and so on.
Considering everything that we've said about resources, if we decided to expose a customer resource and we want to insert a new customer, the URI might be as follows:
POST https://<HOST>/customers
The hypothetical request body might be as follows:
{ "fist-name" : "john", "last-name" : "doe", "e-mail" : "john.doe@email.com" }
Imagine that the previous request will result in a customer ID of 445839 when it needs to recover the customer. The GET method could be called as follows:
GET https://<HOST>/customers/445839
The response will look something like this:
sample body response for customer #445839: { "customer-id": 445839, "fist-name" : "john", "last-name" : "doe", "e-mail" : "john.doe@email.com" }
The same URI can be used for the PUT and DELETE operations, respectively:
PUT https://<HOST>/customers/445839
The PUT body request might be as follows:
{ "last-name" : "lennon" }
For the DELETE operation, the HTTP request to the URI will be as follows:
DELETE https://<HOST>/customers/445839
Moving on, based on the naming conventions, the product URI might be as follows:
POST https://<HOST>/products sample body request: { "name" : "notebook", "description" : "and fruit brand" } GET https://<HOST>/products/9384 PUT https://<HOST>/products/9384 sample body request: { "name" : "desktop" } DELETE https://<HOST>/products/9384
Now, the next step is to expose the URI for order creation. Before we continue, we should go over the various ways to expose the URI. The first option is to do the following:
POST https://<HOST>/orders
However, this could be outside the context of the desired customer. The order exists without a customer, which is quite odd. The second option is to expose the order inside a customer, like so:
POST https://<HOST>/customers/445839/orders
Based on that model, all orders belong to user 445839. If we want to retrieve those orders, we can make a GET request, like so:
GET https://<HOST>/customers/445839/orders
As we mentioned previously, it is also possible to write hierarchical concepts when there is a relationship between resources or entities. Following the same idea of orders, how should we represent the URI to describe items within an order and an order that belongs to user 445839?
First, if we would like to get a specific order, such as order 7384, we can do that like so:
GET https://<HOST>/customers/445839/orders/7384
Following the same approach, to get the items, we could use the following code:
GET https://<HOST>/customers/445839/orders/7384/items
The same concept applies to the create process, where the URI is still the same, but the HTTP method is POST instead of GET. In this scenario, the body also has to be sent:
POST https://<HOST>/customers/445839/orders/7384 { "id" : 7834, "quantity" : 10 }
Now, you should have a good idea of what the GET operation offers in regard to orders. The same approach can also be applied so that you can go deeper and get a specific item from a specific order and from a specific user:
GET https://<HOST>/customers/445839/orders/7384/items/1
Of course, this hierarchy applies to the PUT, PATCH, and POST methods, and in some cases, the DELETE method as well. It will depend on your business rules; for example, can the item be deleted? Can I update an order?
As APIs are being developed, gathering more business rules for their context on a day-to-day basis, generating tech debits and maturing, there often comes a point where teams need to release breaking functionality. It is also a challenge to keep their existing consumers working perfectly. One way to keep them working is by versioning APIs.
Breaking changes can get messy. When something changes abruptly, it often generates issues for consumers, as this usually isn't planned and directly affects the ability to deliver new business experiences.
There is a variant that says that APIs should be versionless. This means that building APIs that won't change their contract forces every change to be viewed through the lens of backward compatibility. This drives us to create better API interfaces, not only to solve any current issues, but to allow us to build APIs based on foundational capabilities or business capabilities themselves. Here are a few tips that should help you out:
Including the version in the URL is an easy strategy for having the version number added at the end of the URI. Let's see how this is done:
https://api.domain.com/v1/ https://api.domain.com/v2/ https://api.domain.com/v3/
Basically, this model tells the consumers which API version they are using. Every breaking change increases the version number. One issue that may occur when the URI for a resource changes is that the resource may no longer be found with the old URI unless redirects are used.
In regard to versioning in the URL, subdomain versioning puts the version within the URI but associated with the domain, like so:
https://v1.api.domain.com/ https://v2.api.domain.com/ https://v3.api.domain.com/
This is quite similar to versioning at the end of the URI. One of the advantages of using a subdomain strategy is that your API can be hosted on different servers.
Another approach to versioning is using MIME types to include the API version. In short, API producers register these MIME types on their backend and then the consumers need to include accept and content-type headers.
The following code lets you use an additional header:
GET https://<HOST>/orders/1325 HTTP/1.1 Accept: application/json Version: 1 GET https://<HOST>/orders/1325 HTTP/1.1 Accept: application/json Version: 2 GET https://<HOST>/orders/1325 HTTP/1.1 Accept: application/json Version: 3
The following code lets you use an additional field in the accept/content-type header:
GET https://<HOST>/orders/1325 HTTP/1.1 Accept: application/json; version=1
GET https://<HOST>/orders/1325 HTTP/1.1 Accept: application/json; version=2
GET https://<HOST>/orders/1325 HTTP/1.1 Accept: application/json; version=3
The following code lets you use a Media type:
GET https://<HOST>/orders/1325 HTTP/1.1 Accept: application/vnd.<host>.orders.v1+json
GET https://<HOST>/orders/1325 HTTP/1.1 Accept: application/vnd.<host>.orders.v2+json
GET https://<HOST>/orders/1325 HTTP/1.1 Accept: application/vnd.<host>.orders.v3+json
When using a RESTful service, it is highly recommended that you use header-based versioning. However, the recommendation is to keep the version in the URL. This strategy allows the consumers to open the API in a browser, send it in an email, bookmark it, share it more easily, and so on. This format also enables human log readability.
There are also a few more recommendations regarding API versioning:
In this article, we learned about best practices related to RESTful web services such naming conventions, and API versioning formats.
Next, to look at how to design RESTful web services with OpenAPI and Swagger, focusing on the core principles while creating web services, read our book Hands-On RESTful Web Services with TypeScript 3.
7 reasons to choose GraphQL APIs over REST for building your APIs
Which Python framework is best for building RESTful APIs? Django or Flask?