37

I am pretty new to Node.js and I am facing the following issue.

My middleware started with the link api/v1/login and a bunch of endpoints. Then api/v1.1 introduced 2 more endpoints. api/v1.2 is now the last and got some new endpoints.

How shall I handle this api versioning in an efficient way? How do you make endpoints from a version available to the next versions as well?

Paolo.nl
  • 431
  • 1
  • 4
  • 7

3 Answers3

55

First of all, if you are building a REST API and you have just started, you may want to consider using Restify instead of Express. While Express can certainly be used for this purpose, Restify has been designed with all the requirements for a REST API server: standardized exceptions, API versioning, etc.

Thus said, I believe your first problem is a design flaw. You should create separate endpoints only when the new APIs are not backward-compatible with the previous version, i.e. when the major version is increased (for example from v1 to v2). This should happen as infrequently as possible!
If you are only adding new features or making other tweaks that do not break existing code, then you should not create a different endpoint. So, you should not create endpoints for v1.1, v1.2, etc, providing that all the code that works with v1.0 will also work with v1.1 (if that's not the case, then you're introducing changes that are not backward-compatible, and thus you should consider changing the version to v2).
Note that every time that you introduce backward-incompatible changes all your users will need to update their code, and you will have to support the old APIs for a period of time sufficient to let all your users update. This is an expensive process, for you (you need to maintain old codebases) and your users as well (they need to update their code), and thus should happen as infrequently as possible. Additionally, for each version you need to write documentation, create examples, etc.
(Bottom line: spend a lot of time designing your API server so it will likely last without backward-incompatible changes for as long as possible)

To answer your question, then, a way to do that could be creating subfolders for each API set (each version), and then set the router accordingly. For example, your project will look like:

/
-- app.js
-- routes/
-- -- v1/
-- -- -- auth.js
-- -- -- list.js
-- -- v2/
-- -- -- auth.js
-- -- -- list.js

That should not be a problem: since v2 is not backward-compatible with v1, chances are that the two files are quite a lot different.
Then, on Express just use the router accordingly. For example:

app.get('/v1/list/:id', v1.list)
app.all('/v1/auth', v1.auth)

app.get('/v2/list/:id', v2.list)
app.all('/v2/auth', v2.auth)

There are other options, however. For example, a more elegant (though slightly advanced) solution can be: http://j-query.blogspot.ca/2013/01/versioned-apis-with-express.html

Note on this method

While, as per semver, every backward-incompatible change should see an increase in the major version of the APIs, if you plan to implement many and substantial differences between v1 and v2 (with very little possibility to re-use code), then this approach is not for you.

In this last case, you may want to create two separate Node.js apps for v1 and v2, and then configure the proper routing using nginx. Versioning will not be done at the app level (each app will respond to '/auth', '/list/:id' and NOT '/v1/auth', '/v1/list:id', etc), but nginx will forward requests with prefix '/v1/' to one worker server, and those with prefix '/v2/' to the other.

ItalyPaleAle
  • 7,185
  • 6
  • 42
  • 69
  • Indeed I created new versions of the API just because I added new features and this is what created the confusion. All the changes I added were backward compatible so from what I read, adding a new version in this case is not necessary. – Paolo.nl Oct 04 '14 at 09:47
  • If I follow your concept so my structure will be look like following in the future: `/ -- controllers/ ---- v1/ ---- v2/ -- helpers/ ---- v1/ ---- v2/ -- middlewares/ ---- v2/ -- models/ ---- v1/ ---- v2/` – user1791139 Feb 26 '15 at 10:18
  • @user1791139 you wouldn't want to do that. See my updated answer for an idea on what you could do in this case. – ItalyPaleAle Feb 26 '15 at 15:30
  • If I copy all those files here and there doesn't this create a lot of duplication. Consider this scenario, if I just have a small change in v1 api (let's say auth model) and require that change to reflect in v2 with all the existing things in v1. Is there any other way to handle this – Fayaz Apr 15 '20 at 16:50
  • where do you put controllers and services inside this? is it inside v1 folder or is it outside – PirateApp Nov 24 '20 at 04:14
15

Frameworks like restify are better suited for api versioning, but if you are using express and need a lightweight module to version your routes, try this npm module https://www.npmjs.com/package/express-routes-versioning

Module allows individual routes to be versioned separately. It supports basic semver versioning on the server to match multiple versions. (if needed). It is agnostic about specific versioning strategies and allows the application to set the version.

Sample code

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

I guess your API violates the REST constraints, at least the stateless constraint certainly. Check the uniform interface constraint of REST. It tells you how to decouple the clients from the implementation of the API. After you did that you probably won't need versioning anymore.

If you don't want to apply REST constraints, then I think the URL should contain only the major version number (to indicate non-backward compatible changes). After that you can define vendor specific MIME types or content type parameters in which you can describe the minor, review and build version numbers if you want. So your clients should send accept and content-type headers with these version parameters.

Just to mention, if you want to support multiple versions at once, you have to write documentation for each of them.

inf3rno
  • 24,976
  • 11
  • 115
  • 197