Semantic Versioning for Express APIs
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. Here is a quick guide on using semantic versioning for Express APIs.
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 we will expose each version to the clients. For example, will we be using 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
That is the basics of how Semantic Versiniong works and the different strategies to implement them.