API's need to change over time. Features are added, bugs are fixed, and changes are made. How can you introduce and track changes without breaking client applications? API versioning is the answer. By versioning your API, you work towards building a robust and scalable product.
What is API versioning?
Versioning an API is the process that allows tracking changes and managing the API's various iterations. Essentially, versioning allows you to create multiple API versions that that coexist but operate independently of each other. That way new features can be added, updates can be made, and old features can be removed with minimal disruption of service to the user.
Why is API versioning important?
Proper versioning is a crucial step to keep projects flexible and ensure compatibility with existing and new tools. Without proper versioning, modifications to the API could cause unexpected errors and result in disruptions for the client. You’ll likely need to make changes to your API over time, so it’s a good idea to analyze whether or not implementing proper API versioning from the start would be a good idea.
A good API versioning strategy not only helps to make projects more flexible, but it can also make projects compatible with more tools and protect backwards compatibility. Over the course of the project, it can also help lower the cost of introducing new features and help communicate changes clearly to the users. Since each API version number gets its own release notes, migration guides, and updated documentation, this strategy promotes a trusting relationship with the user.
When should you version an API?
If you're going to introduce major changes to the API, it would be a good idea to consider adopting an API versioning strategy. As different versions become available, users can incrementally opt-in to new features at their own pace. Versioning can also facilitate making security updates without forcing API users into upgrades that would require downtime to incorporate.
In a situation where the API will support multiple client platforms, API versioning will allow the user to stick with their platform's SDK without being worried about updates for other platforms, and that's something we can help with — liblab offers an robust and comprehensive suite of tools to generate SDKs tailored to your API.
When should you not version an API?
Versioning isn't the best solution for every situation, though. Developing a full versioning strategy for minor updates or bug fixes will more likely add confusion than benefits. Also, in situations where there is only one or two users, such as an API that will only be used internally, it's probably more practical to just update both server and client at once. Same goes if you’re introducing a non-breaking or temporary change, or something on a branch that won't impact any clients.
How to do API versioning
If you think API versioning will be a good fit, you need to understand how to adapt API versioning to suit your needs. One of the first things you’ll want to consider is how you want to label your versioning. There’s a few options:
-
Semantic Versioning (commonly referred to as SemVer) follows a
MAJOR.MINOR.PATCH
format. For more information on semantic versioning, semver.org is a good resource. It’s helpful for tracking backward-compatible changes, functionality, and bug fixes. Each of the major breaking changes are incremented as a new major version number, while backward-compatible additions and bug fixes are each just a minor version number. -
Date-based versioning tags make every API version number the date it was released, which might be useful in some situations where a chronological sequence of releases is more relevant than semantic clarity.
-
Endpoint-based versioning may be helpful in limited situations where the scope of the version will only affect certain endpoints with independent resources.
There isn’t consensus on the “best” approach, it really depends on what information will help you better track the changes made to the API. Analyzing your needs and desired results will help you decide which system will work best for you.
Types of API versioning
Next, you need to decide how the user specifies which API version they want to use. Here are some options:
Versioning Type | Basics | Example | Positives | Negatives |
---|---|---|---|---|
URI Versioning | The version numbers are incorporated into a URL path | http://www.example.com/api/1/products | Easy to understand and implement Clearly separated API versions | Can become cluttered. Not recommended by REST architecture |
Query Parameter | The version number is appended as a query parameter in the API endpoint | http://www.example.com/api/products?version=1 | Clear separation of API versions Easy to implement | Less intuitive for API consumers. Can result in long cluttered URLs |
Header Based | The version number is a specific and unique header field | curl -H “Accepts-version: 1.0” <http://www.example.com/api/products> | Follows REST principles Keeps URI focused on the resources | Less intuitive. More effort is needed to check API request |
Content Negotiation | The version is based on the representational state or media type | curl -H “Accept: application/vnd.xm.device+json; version=1” <http://www.example.com/api/products> | Smaller footprint. No need for URI routing rules. Versions resource representations instead of entire API | Less accessible for testing and exploring via browser |
Again, each of these techniques have different objectives and advantages, so your specific project requirements should determine which technique is the best for you.
How to build an API versioning strategy
Once you’ve planned out what methods and techniques will best suit your constraints and objectives, you ’re ready to formulate your strategy according to API versioning best practices. You’ll need to assess the project scope and define what your versioning policy will be. REST (Representational State Transfer) is a popular API architecture for building web services in which resources are accessed via standard HTTP methods. Using versioning with a REST API allows the developer to add new features to the API, fix bugs, and remove old functionality without breaking anything for the API consumers. If you’re building a REST API, there are a few principles regarding versioning that you might want to keep in mind. Here are some of those recommended api versioning strategies:
1. Communicate changes clearly
The whole point of using a REST API is so that there’s no confusion between client and server about where to access resources. That completely breaks down if you haven’t clearly communicated to the API consumers when things change on the server. You’ll need to consider release notes, migration guides, and updated API documentation to keep everyone on the same page. Perhaps it’d even be worth considering a longer time table to give users enough time to prepare for and implement updates.
2. Use Semantic Versioning
We talked about some of the other options, but semantic versioning is best in line with REST principles. Why? Because REST APIs are stateless; endpoints aren’t affected by outside constraints and function independently from one another. They (in theory, unless you really need it) shouldn’t be fixed to anything in the real world affecting their output, like the date of the most recent new version. Setting up SemVer isolates the endpoints from anything resembling state even further.
3. Maintain backwards compatibility when possible
REST APIs are uniform and consistent. In an ideal world, there would never be any breaking changes. In reality, that’s difficult to implement long-term, but always lean on the side of backwards compatibility. For example, new endpoint parameters should have default values. New features should get their own new endpoints and their own new version. Removing existing fields from API responses is also frowned upon for this reason, especially if you have a good deprecation strategy.
4. Deprecate old versions gradually
What does a good deprecation strategy look like? A clear timeline. Support old versions during the deprecation period and make sure that deprecation endpoints are recorded clearly in the API documentation. Also, be clear with the user. Make sure they’re on the same page about why older versions are being deprecated, what the benefits of upgrading to the new version are, what issues they might face, and how they make solve those issues. Ample support during the transition period will help minimize disruptions and promote trust between the developer and API consumers.
API versioning best practices
Many aspects of your API versioning strategy will be dependent on factors unique to your application, but there are some general guidelines of API versioning best practices to take into consideration.
1. Prioritize the docs
The current state of the entire API should be reflected in comprehensive documentation, customized to the latest API version. Make sure that there are clear instructions on how changes should be introduced with each new version so no user gets confused — you’d be surprised how little friction it takes to make some users jump ship.
2. Keep the line open with the clients
Good communication is key. Understand what your consumers need and how each new version will affect their workflow, not just yours. Establish good channels of communication in advance to inform users of each upcoming change and each new version. Those channels also let you gather feedback from users to understand what their needs are and what their expectations are, and that’ll help you build a roadmap for the future of your API.
3. Plan for security and scalability
While most of API versioning focuses on the functional aspect of the API, security and scalability should be taken into consideration. As new versions are introduced that protect against security threats, older versions may continue to have those since-fixed vulnerabilities. Also, if you build a good API, you’ll start eventually getting increased usage (both over time and in quick spikes) and larger data volumes. The solution? Bake in automated security checks, vulnerability assessments, and scalable practices from the start. Make security updates and patch versions a priority for every version you support, not just the latest version. That includes every major or minor patch, even the since-replaced ones. This is an even bigger area where communication is crucial, since there may even be legal obligations to inform API users of how their data security is being protected in each new update.
4. Test thoroughly
You want to catch as many issues as possible before the API gets to the user. Conduct unit tests, integration tests, and regression tests for each new version. We’ve all been through the frustration of doing an upgrade on one part of a project just to find that we accidentally broke everything else. Thorough testing at each stage of API versioning helps avoid those situations and ensures a reliable product for the user. Automated tools can greatly streamline the process.
How to test API versions
To start, you want to thoroughly test the new API version separately to ensure that it meets all the functional specifications that the new API version is supposed to meet. There’s a couple ways to do this:
1. Unit testing
Unit testing involves testing individual pieces of code. For example, does each endpoint still function like expected? Take an endpoint that just takes in a letter, and if its within a certain range of ASCII values, it’ll shift the letter by however many places you specify. Here’s a function that does that:
const shiftLetter = (letter, key, rangeStart, rangeEnd) => {
const rangeLength = rangeEnd - rangeStart;
const code = letter.charCodeAt(0);
if (rangeStart <= code && code < rangeEnd) {
let n = code - rangeStart + key;
if (n < 0) n = rangeLength - (Math.abs(n) % rangeLength);
return String.fromCharCode((n % rangeLength) + rangeStart);
} else return letter;
};
These examples are from an Algolia article about unit testing. If we have standardized our API, we don’t even need to test this over HTTP requests since that part acts predictably. We can just write a little function like this to test this particular unit of code:
const testShiftLetter = () => {
if (shiftLetter("L", 3, 65, 91) != "O") throw "Assertion error"; // test basic case
if (shiftLetter("s", 14, 65, 122) throw "Assertion error"; // test wrap around, and custom ranges
};
All it does is throw an error if the function doesn’t produce the correct result. You can also measure the performance of individual units here. Each new API version requires that you rerun these tests to make sure each individual piece of the API still works as you expect, so you should build this into your automated workflow, perhaps using a tool like GitStream.
2. Integration testing
Integration testing is very similar to unit testing (some don’t even make the distinction). The difference is that now we’re testing how units of code work together to produce the right result. Here's a more complex example from that same article:
const testCaesar = () => {
if (caesar("HELLO", 1) != "IFMMP") throw "Assertion error"; // test basic case
if (caesar(caesar("DECRYPTED TEXT", 19), -19) != "DECRYPTED TEXT") throw "Assertion error"; // test negative keys for decryption
};
See how it tests expected output even in edge cases?
3. System testing
The last type of testing involves using an application built with the API, testing how everything works together. This is harder to implement, but since you’ve built such great documentation and migration guides for each new version, you likely have demos built with your API that you can test with.
How can liblab help with API versioning
One sticking point a lot of developers have with API versioning is how it interacts with the various SDKs that actually consume the API. That’s where we come in — liblab can analyze your spec and generate SDKs tailored to the needs of your API. Trying to support multiple API versions and making sure clients can abstract API complexities and maintain consistent interfacing is usually a nightmare, but how to version APIs more effectively using SDKs.. liblab’s user-friendly controls let you automatically generate flexible SDKs that include all the necessary components right out of the box.
Conclusion
It may seem daunting to consider all these factors at the beginning of a project, but the time and effort now will pay dividends through the entire lifecycle of the project. If you're in a situation where it makes sense to create an API versioning strategy, the hard work right now will definitely be worth it! Thoughtful planning and implementation of best practices will result in robust scalable APIs and ensure long-term stability and adaptability. It's important to remember that software development is an evolving landscape, so we as devs have to keep up to date with improved best practices and new methods. Doing that puts you well on your way towards creating APIs with smooth transitions between versions, enhancing the end user experience, and helping you to build strong relationships with satisfied customers.