Semantic Versioning for Express APIs

What is Semantic Versioning?

Chameera Dulanga
Bits and Pieces

--

Software versioning is the process of assigning a unique identifier for a specific state of a software application, API, or library. As developers, it is essential to follow a formal convention for versioning since it helps to communicate the changes and their impact.

Semantic versioning is one of the most used and straightforward versioning conventions available, and it provides a three-part version number for your application.

We’ll first look at what semantic versioning is, how you can implement it, and how you can use tools like Bit to better manage and version your components.

What is Semantic Versioning

Semantic versioning is a three-part number versioning system. The version number is in the format of x.y.z (major.minor.patch).

Each part of this number has a unique purpose:

  • Major is only incremented when there are breaking changes for clients where the API isn’t backward compatible (e.g. 1.0.0 -> 2.0.0).
  • Minor is incremented when there are new functionalities that are backward compatible (e.g. 1.0.0 -> 1.1.0).
  • Patch is incremented when the API version has only bug fixes (e.g. 1.0.0 -> 1.0.1).

You can make these version bumps manually or use NPM libraries like semantic-release to automate things. Besides, you need to ensure that each version can operate at scale.

However, handling different versions can get more complicated regarding APIs. Whenever we have a major version available, we need a strategy to run multiple versions in parallel and decide on the length of support for each version. Eventually, you need to abandon some of the older versions of the API.

API versioning strategies

Typically we can implement versioning in APIs in two ways. Let’s take a look at them in detail.

External to the API

Create multiple environments where each one serves a different version of the API. This approach is resource-intensive, where it requires managing multiple environments to host each version.

One option is to use containers since we can share the hardware across multiple environments.

Internal to the API

Implement a routing scheme within the API, and provide support for each major change at the code level. Though multiple versions run within the same environment, maintaining the code for each version could become a challenge in the long run.

Besides, we need to decide how to expose each version to the clients. For example, will we use different URL path fragments or HTTP headers? We can often see that the API version is defined in the URL path fragment.

However, you need to provide backward compatibility for the database in both approaches.

In this tutorial, we will be focusing on using the latter approach, which is “Internal to the API”, using Express routes and handling the compatibility in code.

Version handling in Express APIs

First of all, you need to have a basic understanding of Node.js and Express.js before you begin.

Step 1: Create a new Node.js project

Let’s initialize a new Node.js application. If you already have a Node.js project, skip to the next step.

You can use npm init command to initialize the new application. It will prompt a view below where you need to enter several details like name, version, description and author.

package name: (express-api-versioning)
version: (1.0.0)
description: example-project
entry point: (index.js)
test command:
git repository:
keywords: node.js, express
author: chameera
license: (ISC)
{
“name”: “express-api-versioning”,
“version”: “1.0.0”,
“description”: “example-project”,
“main”: “index.js”,
“scripts”: {
“test”: “echo \”Error: no test specified\” && exit 1"
},
“keywords”: [
“node.js”, “exoress”
],
“author”: “chameera”,
“license”: “ISC”
}
Is this OK? (yes)

Step 2: Installing Express.js

Then you need to install Express.js and other third-party libraries for the project. In this tutorial, we have installed Express and CORS libraries.

// Express
npm install express --save
// CORS
npm install cors --save

Step 3: Setting up the Express server

The first step of setting up the Express server is to create a new file called server.js. Then you can make all the necessary configurations like enabling Cross-Origin Resource Sharing (CORS), parsing and routing in this file using Express.js.

const express = require(“express”);
const cors = require(“cors”);
const app = express();var corsOptions = {
origin: “http://localhost:4200"
};
app.use(cors(corsOptions));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const PORT = process.env.PORT || 8080;app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}.`);
});

Once all the configurations are in place, you can open a terminal and test the application using the node server.js command. If there are no errors, you should see the message; Server is running on port 8000 in the terminal.

Step 4: Creating API endpoints for multiple versions

Now, you can create various versions of your endpoints. Here, we have made two folders as api/v1 and api/v2, and these folders will contain the endpoint handlers for each version separately.

  • api/v1/index.js
var express = require(‘express’);
var router = express.Router();
router.get(‘/’, function(req, res, next) {
res.send(‘v1 GET API’);
});
router.post(‘/’, function(req, res, next) {
res.send(‘v1 POST API’);
});
module.exports = router;
  • api/v2/index.js
var express = require(‘express’);
var router = express.Router();
router.get(‘/’, function(req, res, next) {
res.send(‘v2 GET API’);
});
router.post(‘/’, function(req, res, next) {
res.send(‘v2 POST API’);
});
module.exports = router;

Step 5: Configuring routing

After creating the endpoints, you can use the Express router to manage routing for these two versions. We have imported each version to the server.js file and configured two separate routes here. The updated server.js file will look like below:

const express = require(“express”);
const cors = require(“cors”);
const app = express();var corsOptions = {
origin: “http://localhost:4200"
};
var router = express.Router();router.use('/v1', require('./api/v1'));
router.use('/v2', require('./api/v2'));
app.use(cors(corsOptions));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const PORT = process.env.PORT || 8080;app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}.`);
});

That’s basically it. You have successfully provided versioning support for Express APIs. You can restart the application and test the endpoints with Postman.

  • http://localhost:8080/api/v1 — GET, POST
  • http://localhost:8080/api/v2 — GET, POST

A Better Way To Use Semantic Versioning

Now that you have a basic idea about how semantic versioning works, it’s important to understand that there are better ways to version your applications.

Sometimes, when you’re working in large scale applications, you’d have to manually bump the version, or you’ll have to bump an entire version of the entire project when you make a change in a single file.

But, this is where tools like Bit shine. Bit aims to bring about a seamless component driven design onto your application where you can think and build everything in components, just like lego.

Figure: A basic backend component

The figure above illustrates a simple Bit component that deploys a backend function that handles a form submission. As seen above, it’s on version 0.0.5, meaning that Bit has the capability to independently version your components and treat it as its own entity. Not only that, but, Bit can also help you design, build and test your components in its own isolated environment.

Here’s the perfect example where you can use Bit to better version your backend and frontend components:

All you would have to do is to create your components and write a simple command — bit tag and Bit would automatically version your API using Semantic Versioning.

Also watch:

--

--