Beta4
Beta4

Reputation: 111

NodeJS/Express API versioning

I'd like to set up an API versioning, similar to how Stripe does it but I'm not quite sure how to make express do what I need. https://stripe.com/docs/api#versioning

What I'm looking for is, the proper route would be something like:

/api/v1/call

The kicker is, I'd like them to pass in a version revision like stripe allows, so if they sent a header like "API-Version: 2015-08-15", it would map to that specific version of the major version. So, v1 but the version updated on 2015-08-15.

Essentially, if there is an update to the API call that is not backwards compatible, I'd roll a new version for that particular call. Express would be smart enough to know that if a version isn't passed, use the latest. If a version is passed, use the latest version for each call up until the version date.

I'd assume the directory structure would be something like:

And maybe in the call directories, there is a index that checks for the header version and uses the proper file.

So maybe for instance

Thoughts? Ideas?

Upvotes: 7

Views: 11101

Answers (4)

UniAnon
UniAnon

Reputation: 21

https://medium.com/@vgjohn/node-js-api-versioning-with-totoro-node-c2ea1ef3dfba

There's a small package called totoro-node that helps deal with route management for api versioning. It might help to solve some of the problems you're facing. You just write a simple api definition like this and you can control which endpoints or api versions to deprecate or inherit into subsequent api versions. https://www.npmjs.com/package/totoro-node

var app = express()

app.use('/api', totoro.rain({
    v1: {
        "/oAuth": {
            method: "GET",
            deprecated: true,
            endpointImplementation: routes.authRoutes.oAuth
        },
        "/ssoToken": {
            method: "GET",
            endpointImplementation: routes.authRoutes.sso
        }
    },
    v2: {
        "/ssoToken": {
            method: "GET",
            endpointImplementation: routes.authRoutes.sso
        }
    }
}))

Upvotes: 1

prasanna ramanujam
prasanna ramanujam

Reputation: 211

If you are managing version in routes(url) and client sends version in headers then express doesn't provide any elegant way to handle versioning. Also, doing versioning in routes is not restful.

I wrote an simple npm module to solve this problem. https://www.npmjs.com/package/express-routes-versioning

Express routes versioning

Module allows individual routes to be versioned separately. It is agnostic about specific versioning strategies and allows the application to set the version, so you should be able to parse version from headers and set it to req.version in a middleware. It supports semver versioning format and symbols to map multiple versions to single function. Sample code on how it works.

var app = require('express')();
var versionRoutes = require('express-routes-versioning')();
app.listen(3000);
app.use(function(req, res, next) {
    //req.version is used to determine the version
   req.version = req.headers['accept-version'];
   next();
});
app.get('/users', versionRoutes({
   "1.0.0": respondV1, 
   "~2.2.1": respondV2
}));

// curl -s -H 'accept-version: 1.0.0' localhost:3000/users
// version 1.0.0 or 1.0 or 1 !
function respondV1(req, res, next) {
   res.status(200).send('ok v1');
}

//curl -s -H 'accept-version: 2.2.0' localhost:3000/users
//Anything from 2.2.0 to 2.2.9
function respondV2(req, res, next) {
   res.status(200).send('ok v2');
}

By default, if the client version doesn't match the version provided in the server, module servers the latest version callback available in that route. This behavior can be overridden by providing an additional callback. More info and source code available at https://github.com/Prasanna-sr/express-routes-versioning

Upvotes: 6

Randy
Randy

Reputation: 4381

This how I'm handling versioning. Basically you create a new router object and use app.use so that only /api/v1 routes are sent to it. I then use a "fall through" route which catches anything which didn't match and returns a unknown command message. I also renamed the res.json function so that I can add APIversion = 1 to each object that went out (That's in the router.use function call).

Whenever I have a v2 api I'll do this exact same thing but create a new file and use a different app.use path. See below:

app.js

....
app.use('/api/v1', require('./api1.js'));
....

api1.js

var express = require('express');
var router = express.Router();

router.use(function (req, res, next) {
    res._json = res.json;
    res.json = function json(obj) {
        obj.APIversion = 1;
        res._json(obj);
    };
    next();
});

/* ADD ALL YOUR ROUTES HERE */

//Done - catch all - return command failed
router.get('*', function (req, res) {
    res.status = 404;
    res.json({
        success: false,
        message: 'Unknown command'
    });
});

module.exports = router;

Upvotes: 3

cshion
cshion

Reputation: 1213

I think you could set a middleware before all your routes to check headers.

app.use("*",function (req,res,next) {
  var headers = req.headers
  //Process 
  req.apiVersion = "version"
  next()
}
//all your routes

this is a example , but you could manipulate headers in your router instance and then pass req to other route

//v1/call/index.js
//all your routes
app.use("/v1/call",function (req,res){
   var version = req.apiVersion;
   //execute something depending on version

})

Upvotes: 0

Related Questions