8

Currently under development of API with restify and still cannot get used to specifying the API version in headers. It just doesn't seem very user friendly.

Is there any way for version to be part of url?

Example would be:

http://domain.com/api/v1/action

Or even better in my case:

http://api.domain.com/v1/action

Thanks

Tomas
  • 2,676
  • 5
  • 41
  • 51
  • I think that apart from just declaring those routes yourself (`app.get('/v1/action', ...)`), Restify doesn't support this sort of scheme. – robertklep Jan 26 '14 at 16:37
  • @robertklep hm that's what I thought so.. too bad – Tomas Jan 26 '14 at 17:34
  • It seems like it might be easier to do something like /api/action?v=1, though I have yet to figure out a graceful way to handle this. – jczaplew Feb 05 '14 at 17:03
  • 2
    I think using it as a part of url is better than as a parameter. – Tomas Feb 05 '14 at 18:41

2 Answers2

19

Also you can use Restify to define your versions:

var server = restify.createServer({
    name: 'myAPI',
    versions: ['1.0.0', '2.0.0']
});

Then using this middleware with server.pre:

server.pre(function (req, res, next) {
    var pieces = req.url.replace(/^\/+/, '').split('/');
    var version = pieces[0];

    // only if you want to use these routes:
    // /api/v1/resource
    // /api/v1.0/resource
    // /api/v1.0.0/resource
    if (!semver.valid(version)) {
        version = version.replace(/v(\d{1})\.(\d{1})\.(\d{1})/, '$1.$2.$3');
        version = version.replace(/v(\d{1})\.(\d{1})/, '$1.$2.0');
        version = version.replace(/v(\d{1})/, '$1.0.0');
    }

    if (semver.valid(version) && server.versions.indexOf(version) > -1) {
        req.url = req.url.replace(version + '/', '');
        req.headers['accept-version'] = version;
    }

    return next();
});

Finally, in your routes you can do something like this:

server.get({ path: '/resource/:id', version: '1.0.0' }, function () {
  // send object in version 1.0.0
});

server.get({ path: '/resource/:id', version: '2.0.0' }, function () {
  // send object in version 2.0.0
});

Examples:

The above examples follow the standards, because if version is not specified through header or url, shows the last version.

UPDATE:

I made a plugin to have API versions in URL: https://www.npmjs.com/package/restify-url-semver

Marco Godínez
  • 3,440
  • 2
  • 21
  • 30
4

People are correct that it's not supported by restify, but I thought I'd throw my solution to this problem into the mix. I'm doing something like this (warning, untested code to follow):

After I create a server, but BEFORE I declare the routes, I register a custom parser to translate the url-style version specifier into an HTTP-style specifier:

server.use(versionParser);

And versionParser.js looks something like this:

var semver = require('semver');
var restify = require('restify');

module.exports = function (req, res, next) {

    // we expect every request to have the form "/api/[api-version]/..."
    // verify that the api-version is a valid semver value
    var urlPieces = req.url.replace(/^\/+/, '').split('/');
    var api = urlPieces[0];
    var apiVersion = urlPieces[1];

    if (api !== 'api' || !semver.valid(apiVersion)) {
        return next(new restify.InvalidContentError({message: "Invalid Version Specifier"}));
    }

    req.header('Accept-Version', apiVersion);
    return next();
}

This way, the restify routes can inspect the Accept-Version header as they do naturally.

Sidenote: The semver part is probably not relevant to this answer, but I wanted to verify that the API version in the URL was a valid semver value, as it allows flexibility in the URL values such that a user could take advantage of restify's flexibility in version specifiers.

thataustin
  • 2,035
  • 19
  • 18
  • Injecting the request, interesting. I think you would have to do a tiny bit more work to take care of cases when people actually specify the version header, and also need a bit of thought that restify supports versions in format x.x.x but otherwise, good and effective answer – Tomas Feb 22 '14 at 02:16
  • 1
    @Tom Good thoughts. semver.valid does enforce that it's x.x.x, so I _think_ that would cover it. As for checking to see if they initially put it in the Accept-Version header, that's a good thought that I could see being especially useful in the case of a migration from one format to the other. – thataustin Feb 22 '14 at 07:30
  • Honestly I haven't use semver yet so not exactly sure what does it do. But anyways in my case, it would be rather easy without it as well. I don't see when I would be using other version than 1, 2, etc. So simple version+'.0.0' would do – Tomas Feb 23 '14 at 02:47