Restify: API version in URL - node.js

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

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:
http://api.domain.com/resource/10
http://api.domain.com/2.0.0/resource/10
http://api.domain.com/1.0.0/resource/10
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

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.

Related

How do I add parameters to long-form return requests in module.exports routes?

I'm coding for an API connection area, that's predominately graphql but needs to have some REST connections for certain things, and have equivalent to the following code:
foo.js
module.exports = {
routes: () => {
return [
{
method: 'GET',
path: '/existing_endpoint',
handler: module.exports.existing_endpoint
},
{
method: 'POST',
path: '/new_endpoint',
handler: module.exports.new_endpoint // <--- this not passing variables
}
]
},
existing_endpoint: async () => {
/* endpoint that isn't the concern of this */
},
new_endpoint: async (req, res) => {
console.log({req, res})
return 1
}
}
The existing GET endpoint works fine, but my POST endpoint always errors out with the console of {} where {req, res} should have been passed in by the router, I suspect because the POST isn't receiving. I've tried changing the POST declaration in the routes to module.exports.new_endpoint(req, res), but it tells me the variables aren't found, and the lead-in server.js does have the file (it looks more like this...), and doing similar with the server.js, also getting similar results, implying that's probably wrong too. Also, we have a really strict eslint setup, so I can't really change the format of the call.
Every example I've seen online using these libraries is some short form, or includes the function in the routes call, and isn't some long form like this. How do I do a POST in this format?
/* hapi, environment variables, apollog server, log engine, etc. */
/* preceeding library inclusions */
const foo = require('./routes/foo')
const other_route = require('./routes/other_route')
const startServer = async () => {
const server = Hapi.server({port, host})
server.route(other_route.routes())
server.route(foo.routes())
}
This is a bug with Hapi in node v16. I just opened an issue.
Your current solutions are either:
Upgrade to Hapi v20
Use n or another method to downgrade to node v14.16 for this project. I can confirm that POST requests do not hang in this version.

Complex NodeJS / Express REGEX routing

I'm trying to create a NodeJS Express API (route) which has the following characteristics:
It has a base path, in my case it is /web/views. This part is a static value and doesn't change for as long as the server is up.
I can do this as follows:
const BASE = '/web/views'; // defined externally/elsewhere
app.get(BASE, function handleRequest(req, res) {
// handle API request...
}
Next, I expect to be provided with a resource. Given the name of this resource, I locate a file and send it to the client.
I can do this as follows:
app.get(BASE + '/:resource', function handleRequest(req, res) {
var resource = req.params.resource;
// handle API request...
}
So on the client, I invoke it this way:
GET /web/views/header
All of this works so far... but my problem is that my 'resource' can actually be a path in itself, such as:
GET /web/views/menu/dashboard
or a longer path, such as:
GET /web/views/some/long/path/to/my/xyz
I was using the following REGEX mapping:
const DEFAULT_REGEX = '/(\*/)?:resource';
or more precisely:
app.get(BASE + DEFAULT_REGEX, function handleRequest(req, res) {
var resource = req.params.resource;
// handle API request...
}
This works with an arbitrary length path between my BASE value and the :resource identifier, but the problem is that my resource variable only has
the xyz portion of the path and not the full path (ie: /some/long/path/to/my/xyz).
I could simply cheat and strip the leading BASE from the req.url, but I though there would be a REGEX rule for it.
If anyone knows how to do such advanced REGEX routing, I'd appreciate it.
Thanks!
Sure, so I think the easiest way is to simply not worry about using Regex, but instead just use a wildcard. You lose the cool params name, but otherwise it works as you're looking for. For example:
const express = require('express');
const app = express();
const port = 3000;
const BASE = '/web/views'
app.get(`${BASE}/*`, (req, res) => {
res.send(req.url);
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
If you hit http://localhost:3000/web/views/path/to/my/resource, in my example the response content will be /web/views/path/to/my/resource, so from there it's some simple string manipulation to pull the bit you want:
let resource = req.url.split('/web/views')[1];
// resource will equal /path/to/my/resource if the above URL is used
Of course you could get fancier with your string parsing to check for errors and such, but you get the idea.
You could even setup a middleware to get that resource piece for other handlers to work from:
app.use(`${BASE}/*`, (req, res, next) => {
const resource = req.url.split(BASE)[1];
req.resource = resource;
next();
});
Then all subsequent routes will have access to req.resource.

Node.js REST API versioning the right way?

I would like to manage my REST API based on URL version specifying.
For example:
api.mydomain.com/v1/rides/
// will return all rides based on v1.
api.mydomain.com/v2/rides/
// will return all rides based on v2 (probably with some braking changes).
api.mydomain.com/rides/
// will return all rides based on v2, since v2 is the newest.
Thats awesome.
Before we get started dealing with the practical way of handling this,
we should talk about the logical "default newest versioning" - I mean, if user does not going to specify any kind of version, should I serve him with the newest version or throw a 404 not found error?
Should I oblige the user for specifying an API version?
If I do, is there any standard of "parsing" the specific / newest version?
I tell you why im concern about this: Lets say that "Dan" have app installed which relays on the newest API endpoint (V1 for example), then I release V2 which has braking changes.
Since Dans "listens" to the newest version by default, Dans app is going to be crashed.
That is not a good behaviour at all.
Maybe should I prevent using the "default newest versioning"?
Maybe should I use Dans app to listen for a specific version, while remote developers accessing my API as a web service can have the privilege to choose between specific version or the newest by default?
Is there any standard?
**
Now lets talk practically. Lets say that I have a router handling those requests, maybe something like this:
// app.js file
app.use((req, res, next) => {
try {
require('../resources/' + req.url.split('/')[1] + '/' + req.url.split('/')[1] + '-router')(app);
next();
} catch(err) {
dep.cast(res, 404, new Error("Not Found"));
}
});
And some handler, like this:
// resources/rides/rides-router.js file
module.exports = function(app) {
// GET ride - select a ride
app.get("/v1/rides/:id", dep.verifyToken(), require('./api/v1/get-ride'));
app.get("/v2/rides/:id", dep.verifyToken(), require('./api/v2/get-ride'));
// POST ride - insert a new ride
app.post("/v1/rides", dep.verifyToken(), require('./api/v1/set-ride'));
}
As you can see, I have handler which sends the requests to the specific divisions in the API, split by V1, V2, etc..
It makes me wonder if its right to have the same page containing the same function over and over in different folders, one for V1 and one for V2.
Ofcourse, with some braking changes, but they are probably going to be similar. Is not it bordering with repetitive code?
Look at the project structure:
What do you think about this?
Instead of adding version in every route you can add it in app level. So It won't be tightly coupled with API route.
import * as express from 'express';
// v1/get-ride.js
const router = express.Router();
router.post('/rides/:id', dep.verifyToken(), (req, res) => {
// Your code
});
app.use('/v1', router);
// v2/get-ride.js
const router = express.Router();
router.post('/rides/:id', dep.verifyToken(), (req, res) => {
// Your code
});
app.use('/v2', router);
I would recommend using node-express-versioning module instead.
It would help you to support multiple versions without changing the url of API, just send the version of API and direct the call to that version route-controller.
*
*//version taken out from header
app.use(function(req, res, next)
{
req.version = req.headers['accept-version'];
console.log(req.version);
next();
});
//version path defined
app.use('/api', versionRoutes({
"1.0.0": respondV1,
"2.0.0": respondV2
}));
function respondV1(req, res, next)
{
app.use('/api',routeV1);
next();
}
function respondV2(req, res, next)
{
app.use('/api',routeV2);
next();
}*
*
There's no "right way" to do API versioning.
However, URI based global versioning is not at all RESTful.
Phil Sturgeon of "APIs you won't hate" recommends an API Evolution approach instead.
See: https://apisyouwonthate.com/blog/api-evolution-for-rest-http-apis
Have you considered this as an option?

Trying to Update Open API Spec from 2.0 to 3.0 But YAML Still Evaluates as 2.0.

I am trying to update my local swagger project from OpenAPI 2.0 to OpenAPI 3.0.
I am getting a ton of errors which imply that I my YAML file is still being evaluated as OAS2.0:
Project Errors
--------------
#/: Missing required property: swagger
#/: Additional properties not allowed: components,servers,openapi
#/paths/~1auth~1info~1firebase/get/responses/200: Not a valid response definition
#/paths/~1posts~1{id}~1comments/get/responses/200: Not a valid response definition
#/paths/~1posts~1{id}~1comments/get/parameters/2: Not a valid parameter definition
#/paths/~1posts~1{id}~1comments/get/parameters/1: Not a valid parameter definition
#/paths/~1posts~1{id}~1comments/get/parameters/0: Not a valid parameter definition
#/paths/~1users~1{id}~1profile/get/responses/200: Not a valid response definition
I have updated swagger-ui-express to the latest version.
Below are the relevant parts of my server.js file:
const YAML = require('yamljs');
const swaggerDocument = YAML.load('./api/swagger/swagger.yaml');
const SwaggerExpress = require('swagger-express-mw');
const swaggerUI = require('swagger-ui-express');
const api = express();
let swaggerConfig =
{
appRoot: __dirname
}
let swaggerOptions =
{
explorer: true
};
api.use('/api/v1/docs/endpoints',
swaggerUI.serve,
swaggerUI.setup(swaggerDocument, swaggerOptions)
);
SwaggerExpress.create(swaggerConfig, function(err, swaggerExpress) {
if (err) { throw err; }
// install middleware
swaggerExpress.register(api);
});
I have updated swagger-ui-express and swagger-express-mw to the latest versions.
But I am still getting the above errors.
I am thinking that the way forward may be to not use swagger-ui-express
and rework my code for swagger-ui but I see online that people seem to
get things working with that npm module.
Any ideas what I am doing wrong?

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:
/router/
/router/v1
/router/v1/call
/router/v1/anotherCall
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
/router/v1/call/index.js
/router/v1/call/20150810.js -- First version
/router/v1/call/20150815.js -- Updated version that isn't backwards compat.
Thoughts? Ideas?
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
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;
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
}
}
}))
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
})

Resources