Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon

Best practices for RESTful web services : Naming conventions and API Versioning [Tutorial]

Save for later
  • 12 min read
  • 12 Jul 2019

article-image

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.

What are naming conventions


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:

  • Understandability: The resource's representation format should be understandable and utilizable by both the server and the client
  • Completeness: A resource should be completely represented by the format
  • Linkability: A resource can be linked to another resource


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?

What is API versioning


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:

  • Put yourself in the consumer's shoes: When it comes to product perspective, it is suggested that you think from the consumer's point of view when building APIs. Most breaking changes happen because developers build APIs without considering the consumers, which means that they are building something for themselves and not for the real users' needs.
  • Contract-first design: The API interface has to be treated as a formal contract, which is harder to change and more important than the coding behind it. The key to API design success is understanding the consumer's needs and the business associated with it to create a reliable contract. This is essentially a good, productive conversation between the consumers and the producers.
  • Requires tolerant readers: It is quite common to add new fields to a contract with time. Based on what we have learned so far, this could generate a breaking change. This sometimes occurs because, unfortunately, many consumers utilize a deserializer strategy, which is strict by default. This means that, in general, the plugin that's used to deserialize throws exceptions on fields that have never been seen before. It is not recommended to version APIs, but only because you need to add a new optional field to the contract. However, in the same way, we don't want to break changes on the client side. Some good advice is documenting any changes, stating that new fields might be added so that the consumers aren't surprised by any new changes.
  • Add an object wrapper: This sounds obvious, but when teams release APIs without object wrappers, the APIs turn on hard APIs, which means that they are near impossible to evolve without having to make breaking changes. For instance, let's say your team has delivered an API based on JSON that returns a raw JSON array. So far, so good. However, as they continue, they find out that they have to deal with paging, or have to internationalize the service or any other context change. There is no way of making changes without breaking something because the return is based on raw JSON.
  • Always plan to version: Don't think you have built the best turbo API in the world ever. APIs are built with a final date, even though you don't know it yet. It's always a good plan to build APIs while taking versioning into consideration.

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at €18.99/month. Cancel anytime

Including the version in the URL


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.

Versioning in the subdomain


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.

Versioning on media types


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

Recommendation


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:

  • Use only the major version: API consumers should only care about breaking changes.
  • Use a version number: Keep things clear; numbering the API incrementally allows the consumer to track evolvability. Versioning APIs using timestamps or any other format only creates confusion in the consumer's mind. This also exposes more information about versioning than is necessary.
  • Require that the version has to be passed: Even though this is more convenient from the API producer's perspective, starting with a version is a good strategy because the consumers will know that the API version might change and they will be prepared for that.
  • Document your API time-to-live policy: Good documentation is a good path to follow. Keeping everything well-described will mean that consumers avoid finding out that there is no Version 1 available anymore because it has been deprecated. Policies allow consumers to be prepared for issues such as depreciation.


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?

Understanding advanced patterns in RESTful API [Tutorial]