After reading this excellent article about now Netflix handles 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 behaviours & bugs. You end up with split URL paths like /v1 & /v2, and having to update clients (especially clients following an app-store model where there is no guarantee the user will update the app) becomes impossible since the URLs are fixed.

My attempt to solve this problem with an ExpressJS middleware that allows you to semantically version the incoming requests accordingly, and offers a solution 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.

The idea is to default clients to a specific version, e.g. 1.2.5 could be the latest API version, but then allow clients to specify the API version they expect via a HTTP header, e.g. X-API-Version: 1.0.5, which means your backend can respect older versions whilst introducing breaking changes safely for newer versions.

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')));

The idea is you can use Semver expressions to push certain clients down particular routes.

And you can use v with other middleware functions too:

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'));

As the first example shows, the verify function is required in order to ensure that req.v_version will be set to a valid version, either one specified by the client or the maximum version.

There’s also a satisfy method to perform comparisons within routes themselves, like so:

app.use(function (req, res, next) {
  res.data = {
    page: 1,
    count: 12
  };

  if (v.satisfy(req, '>= 2.4.2')) {
    req.data.total = 42;
  }

  next();
});

And beyond that, v makes no assumptions on how you structure your middlewares & routes, especially with regards to versioning, that’s all up to you! You know what’s best for your API!

Back to home

Blog Logo

James D


Published