Splitting Routes by API Version

This little piece of Express middleware will help split your routes based on a ApiVersion header. This allows you to deliver different content or handle different business logic for various clients.

My specific use case was that I needed to change the payload and logic of an API endpoint. However, I still needed to support legacy mobile clients that expected the older payload and logic. The solution was to have the clients pass an ApiVersion header with a proper semver value. This allowed us to ensure the semver value being requested meets the minimum version requirements. Otherwise, it falls back to using the older endpoint logic.

const express = require('express');
const semver = require('semver');

// Middleware
const satisfiesApiVersion = (minApiVersion) => {
    return (req, res, next) => {
        if (semver.lt(req.headers.apiversion, minApiVersion)) {
            return next('route');
        }

        next();
    }
};

const app = express();

// New Content Route
app.get('/my-route', satisfiesApiVersion('2.0.0'), (req, res) => {
    res.send('Version 2.0.0 content');
});

// Legacy Content Route
app.get('/my-route', satisfiesApiVersion('1.0.0'), (req, res) => {
    res.send('Version 1.0.0 content');
});

app.listen(8080);

We're using the semver npm package, which provides semver comparison utilities. If the ApiVersion sent in the headers is less than the minApiVersion specified on the route, then we call next('route') which skips the rest of the route and looks for the next matching route. We could have many different versions of this endpoint as long as they are listed newest to oldest.

Example cURL request which receives the legacy content:

> curl --location --request GET 'localhost:8080/my-route' --header 'ApiVersion: 1.6.3'

Version 1.0.0 content

Example cURL request which receives the new content:

> curl --location --request GET 'localhost:8080/my-route' --header 'ApiVersion: 2.8.1'

Version 2.0.0 content

One last note. HTTP headers are case insensitive so ApiVersion, apiversion, and ApIvErSiOn should all be treated the same.