Why Versioning

For those who wish to scale their system from a legacy, coupled or even single-instance application to a more modern, and scalable system, there are always several general approaches to take. My suggestion is always to separate your front end and back end first and make them be able to run on their own instances without depending on other’s existence.
This separation of concern is the first step to scale and it makes sure the business logic and the front end can be run on two or more containers.
Next, you will need to create ‘trust’ between BE and FE by creating a contract that both parties agree on.
This step can be done by using an API contract manager. In my workplace(HBC), we have our in-house tool for this, APIBuilder, which is now open sourced. There are also several options on the market which do the job well (eg. Swagger).
With a properly documented API contract/definition, front end engineers can work on their own totally independently and vice-versa. And, the biggest benefit of doing this extra work is to make the future work of backward compatibility

Options You Have
  1. URL segment

    In this case, your URL http://example.com/api/resource/detail will become:

    http://example.com/api/v2.0/resource/detail

    This is the easiest and most straightforward solution. But it has the version number as a part of the resource path, making it not fully comply with the concept of REST.

  2. HTTP ‘Accept’ header

    This is a better way to solve the versioning problem. Your HTTP header accept: application/json will become:

    accept: application/vnd.company.myapp-v3+json

  3. Custom header

    Very similar with the second method but instead using the well-known ‘accept’ header, we create another header just to indicate the version number, something like:

    api-version: v2.0

What’s next

Now, let’s talk about implementation. With whichever versioning strategy is chosen, we will need some work to be done on both the client and the server.

client side

Basically, we will need a middleware handler or some equivalent for the client so that we don’t need to duplicate the same code all the time.
Depending on the HTTP client you have, the solution can be very different.
You can refer to this Github repo (superagent-use-middleware) where you can find the middleware library I wrote for ‘superagent’ HTTP client for browsers (so, in JavaScript). In this case, you can wrap the network utility and create an entry point method to specify the API version, something like:

1
2
3
agent.get('example.com/some/resource/detail')
.withVersion(1)
.then(res => {})

Assume the API version is a custom header field. Then will get:

upload successful

server side

Similar to the client side, we just need a way to tell what version the client is requested by query the URL or the header fields. Then use the different route logic for the requested resource.

To achieve this, we can add middleware on the App instance level, or add application load balancer/service mesh to redirect the traffic to the instance which has logic for the requested API version. The second solution is in general better because then we can easily obsolete old endpoints and keep the app clean. But it apparently creates more overhead for developing.

Conclusion

Versioning the API is something we always know we should do, but we rarely do. We only realise when we have to do it when it’s halfway into the project and it gets much more difficult than we doing it in the very beginning. Here I listed three common ways, hope it can be helpful for someone in the future.