Reusable Node Code Modules, Part 13 - Versioning
Maintaining Compatibility: Implementing Reusable Versioning Modules
Versioning is crucial for maintaining backward compatibility, introducing new features, and ensuring stable API behavior over time. Implementing a reusable versioning module ensures consistent version management across projects. This guide covers how to structure reusable versioning modules in Node.js using Express, manage versions efficiently, and integrate common libraries.
Common Libraries and Tools
1. SemVer (Semantic Versioning)
SemVer is a widely-adopted versioning system that uses a three-part version number: MAJOR.MINOR.PATCH.
Key Features
Simplicity: Easy to understand and implement
Clarity: Clear rules for incrementing version numbers
Compatibility: Ensures backward compatibility and proper version management
Adoption: Widely adopted and supported by various tools
2. Express-Version-Route
Express-Version-Route is a middleware for versioning routes in Express applications
Key Features
Route Versioning: Enables versioning at the route level
Middleware: Simple middleware integration with Express
Flexibility: Supports different versioning schemes
Compatibility: Compatible with existing Express routes and middleware
3. URL Versioning
URL Versioning is a common method for versioning APIs by including the version number in the URL
Key Features
Simplicity: Easy to implement and understand
Visibility: Makes versioning explicit in the URL
Flexibility: Supports different versioning schemes
Compatibility: Compatible with most HTTP clients and routers
4. Header Versioning
Header Versioning involves specifying the API version in the HTTP headers
Key Features
Clean URLs: Keeps URLs clean by using headers for versioning
Flexibility: Supports custom versioning schemes
Compatibility: Requires clients to support custom headers
Scalability: Suitable for large-scale APIs with multiple versions
Comparison
SemVer: Best for clear and consistent versioning with wide adoption
Express-Version-Route: Ideal for route-level versioning in Express applications
URL Versioning: Suitable for simple and explicit versioning visible in URLs
Header Versioning: Best for clean URLs and flexible versioning schemes using headers
Examples and Wrapper Class
Example 1: SemVer
Setup:
$ npm install semver
Configuration:
import semver from 'semver';
const currentVersion = '1.0.0';
const checkVersion = (clientVersion) => {
if (semver.lt(clientVersion, currentVersion)) {
throw new Error('Client version is outdated');
}
return true;
};
export default checkVersion;
Wrapper Class:
class VersionManager {
constructor() {
this.currentVersion = '1.0.0';
}
checkVersion(clientVersion) {
if (semver.lt(clientVersion, this.currentVersion)) {
throw new Error('Client version is outdated');
}
return true;
}
incrementVersion(type) {
this.currentVersion = semver.inc(this.currentVersion, type);
}
getVersion() {
return this.currentVersion;
}
}
export default new VersionManager();
Example 2: Express-Version-Route
Setup:
$ npm install express express-version-route
Configuration:
import express from 'express';
import versionRoute from 'express-version-route';
const app = express();
const v1Routes = express.Router();
v1Routes.get('/data', (req, res) => {
res.json({ version: 'v1', data: 'Data from version 1' });
});
const v2Routes = express.Router();
v2Routes.get('/data', (req, res) => {
res.json({ version: 'v2', data: 'Data from version 2' });
});
app.use('/api', versionRoute([
{ version: '1.0.0', router: v1Routes },
{ version: '2.0.0', router: v2Routes }
]));
app.listen(3000, () => {
console.log('Server started on http://localhost:3000');
});
Wrapper Class:
class VersionedRouter {
constructor() {
this.routes = [];
}
addRoute(version, router) {
this.routes.push({ version, router });
}
getRouter() {
return versionRoute(this.routes);
}
}
export default new VersionedRouter();
Example 3: URL Versioning
Setup:
$ npm install express
Configuration:
import express from 'express';
const app = express();
app.get('/api/v1/data', (req, res) => {
res.json({ version: 'v1', data: 'Data from version 1' });
});
app.get('/api/v2/data', (req, res) => {
res.json({ version: 'v2', data: 'Data from version 2' });
});
app.listen(3000, () => {
console.log('Server started on http://localhost:3000');
});
Wrapper Class:
class VersionedRouter {
constructor() {
this.app = express();
}
addRoute(version, path, handler) {
this.app.get(`/api/${version}${path}`, handler);
}
listen(port) {
this.app.listen(port, () => {
console.log(`Server started on http://localhost:${port}`);
});
}
}
export default new VersionedRouter();
Example 4: Header Versioning
Setup:
$ npm install express
Configuration:
import express from 'express';
const app = express();
const checkVersion = (req, res, next) => {
const version = req.headers['api-version'];
if (!version) {
return res.status(400).json({ error: 'API version header missing' });
}
req.version = version;
next();
};
app.use(checkVersion);
app.get('/data', (req, res) => {
if (req.version === '1.0.0') {
res.json({ version: 'v1', data: 'Data from version 1' });
} else if (req.version === '2.0.0') {
res.json({ version: 'v2', data: 'Data from version 2' });
} else {
res.status(400).json({ error: 'Unsupported API version' });
}
});
app.listen(3000, () => {
console.log('Server started on http://localhost:3000');
});
Wrapper Class:
class VersionedRouter {
constructor() {
this.app = express();
this.app.use(this.checkVersion);
}
checkVersion(req, res, next) {
const version = req.headers['api-version'];
if (!version) {
return res.status(400).json({ error: 'API version header missing' });
}
req.version = version;
next();
}
addRoute(path, handler) {
this.app.get(path, handler);
}
listen(port) {
this.app.listen(port, () => {
console.log(`Server started on http://localhost:${port}`);
});
}
}
export default new VersionedRouter();