After reading this excellent article from Netflix about scaling & versioning, and also from
experience, it's safe to say that writing a versioned API is a pain. Supporting older "legacy" clients is tedious and
often leads to unexpected behaviour. You end up with split URL paths like /v1
& /v2
, and having to update clients
(especially clients following an app-store model) becomes impossible since the URLs are fixed.
v
attempts to solve this problem by offering you a way to semantically version the incoming requests accordingly, and
offers an easy programmable way to control your logic across API versions 😎 Rather than sending versions from
your clients & having complicated if-statements throughout your codebase (IF version >= 100 AND version < 200
) instead
you have a simpler way to compare versions.
This would not be possible without the Semver package from NPM, so a big thank you to them 👍
var express = require('express');
var v = require('vpi');
var app = express();
app.use(v.verify());
app.use('/users', v('>= 2.0.0', require('./v2/users')));
app.use('/users', v('>= 1.0.0 && < 2.0.0', require('./v1/users')));
Then all you need to do is send a fixed version with your request:
GET /users HTTP/1.1
X-API-Version: 1.2.1
This can be a consistent version across all the requests from the client, or each request can have it's own iterations & version numbers. That's entirely up to you! It's your client & your codebase, you know what is best for you!!
By default, omitting the version header sets the request's version to "INF"
, which is considered to be the maximum
potential version available. This means that omitting the version also forces the request to behave like a bleeding-edge
build, although you can override the latest build number & the default build number by passing an object to the
verify method 😄
You can also use v
with middleware functions too, like so:
app.use(v('>= 2.0.0', middleware.v2version));
app.use(v('>= 1.0.0 && < 2.0.0', middleware.v1version));
app.use((req, res) => res.render('some-page'));
v([condition], [handler])
The condition is any range that Semver accepts, and the handler is your standard (req, res, next)
function (including Express routes, etc), just like similar routing packages like vhost.
The satisfy
method is Semver's own satisfy function exposed so you can easily do synchronous version
checks in your own logic, for example to add specific properties to later versions, like so:
app.use((req, res, next) => {
res.data = {
page: 1,
count: 12
};
if (v.satisfy(req, '>= 2.4.2')) {
req.data.total = 42;
}
next();
});
The verify
method is a middleware that should be called early on. It ensures that req.v_version
will be set to a
valid version, either one specified by the client or the maximum version. It accepts an object of options to customise
how and which version will be set on a per-request basis:
Property | Description |
---|---|
header |
The name of the request header where the version is being sent |
latest |
Manually set the latest version that the middleware will fall back to |
default |
Set the default version that will be assigned to the request if no version header is set, defaults to latest |
app.use(v.verify({
header: 'X-Client-Version'
latest: '2.4.2'
}));
This will also add the X-Client-Version
header to the req
and res
, so you can include the current version in your
application (as req.get('X-Client-Version')
).