I have a route mapped as:
app.get('/health/*', function(req, res){
res.send('1');
});
How can I remove / remap this route to an empty handler at runtime?
This removes app.use middlewares and/or app.VERB (get/post) routes. Tested on express#4.9.5
var routes = app._router.stack;
routes.forEach(removeMiddlewares);
function removeMiddlewares(route, i, routes) {
switch (route.handle.name) {
case 'yourMiddlewareFunctionName':
case 'yourRouteFunctionName':
routes.splice(i, 1);
}
if (route.route)
route.route.stack.forEach(removeMiddlewares);
}
Note that it requires that the middleware/route functions have names:
app.use(function yourMiddlewareFunctionName(req, res, next) {
... ^ named function
});
It won't work if the function is anonymous:
app.get('/path', function(req, res, next) {
... ^ anonymous function, won't work
});
Express (at least as of 3.0.5) keeps all of its routes in app.routes. From the documentation:
The app.routes object houses all of the routes defined mapped by the associated HTTP verb. This object may be used for introspection capabilities, for example Express uses this internally not only for routing but to provide default OPTIONS behaviour unless app.options() is used. Your application or framework may also remove routes by simply by removing them from this object.
Your app.routes should look similar to this:
{ get:
[ { path: '/health/*',
method: 'get',
callbacks: [Object],
keys: []}]
}
So, you should be able to loop through app.routes.get until you find what you are looking for, and then delete it.
The above approach requires you have a named function for the route. I wanted to do this as well but didn't have named functions for routes so I wrote an npm module that can remove routes by specifying the routing path.
Here you go:
https://www.npmjs.com/package/express-remove-route
It is possible to remove mounted handlers (added with app.use) while the server is running, although there is no API to do this, so it isn't recommended.
/* Monkey patch express to support removal of routes */
require('express').HTTPServer.prototype.unmount = function (route) {
for (var i = 0, len = this.stack.length; i < len; ++i) {
if (this.stack[i].route == route) {
this.stack.splice(i, 1);
return true;
};
}
return false;
}
This is something I need, so it's a shame there isn't a proper api, but express is just mimicing what connect does here.
app.get$ = function(route, callback){
var k, new_map;
// delete unwanted routes
for (k in app._router.map.get) {
if (app._router.map.get[k].path + "" === route + "") {
delete app._router.map.get[k];
}
}
// remove undefined elements
new_map = [];
for (k in app._router.map.get) {
if (typeof app._router.map.get[k] !== 'undefined') {
new_map.push(app._router.map.get[k]);
}
}
app._router.map.get = new_map;
// register route
app.get(route, callback);
};
app.get$(/awesome/, fn1);
app.get$(/awesome/, fn2);
And then when you go to http://...awesome fn2 will be called :)
Edit: fixed the code
Edit2: fixed again...
Edit3: Maybe simpler solution is to purge routes at some point and repopulate them:
// remove routes
delete app._router.map.get;
app._router.map.get = [];
// repopulate
app.get(/path/, function(req,res)
{
...
});
You can look into Express route middleware and possibly do a redirect.
As already mentioned above, the new Express API doesn't seem to support this.
Is it really necessary to completely remove the mapping? If all you need is to stop serving a route, you can easily just start returning some error from the handler.
The only (very odd) case where this wouldn't be good enough is if dynamic routes were added all the time, and you wanted to completely get rid of old ones to avoid accumulating too many...
If you want to remap it (either to do something else, or to map it to something that always returns an error), you can always add another level of indirection:
var healthHandler = function(req, res, next) {
// do something
};
app.get('/health/*', function(req, res, next) {
healthHandler(req, res, next);
});
// later somewhere:
healthHandler = function(req, res, next) {
// do something else
};
In my opinion this is nicer/safer than manipulating some undocumented internals in Express.
There is no official method but you can do this with stack.
function DeleteUserRouter(appName){
router.stack = router.stack.filter((route)=>{
if(route.route.path == `/${appName}`){
return false;
}
return true;
});
}
appName is path name .
Filter the methods in the router.route.stack where the router is express.Router
or you can do the same for the app but with app._router.stack.
Note: For below 4.0 use - app.router.stack.
Related
I think I'm missing somthing simple...
Simply put, why does this work:
router.use('/:object', require('../v1/routes'));
But this not? [EDITED after Evert's answer, below]
function getRouter(req, res, next) {
return require('../v1/routes');
}
router.use('/:object', getRouter());
where '../v1/routes' contains simply:
const express = require('express');
const router = express.router({ mergeParams: true });
router.get('/', getAll);
function getAll(req, res, next) {
res.send("Hello");
}
module.exports = router;
I was hoping to dynamically require the files using the API version as a filesystem base. Further up the chain I get the API version as part of the URL, so I was hoping to use that to drive the directory for the included routing file:
function getRouter(req, res, next) {
return require(`../${req.params.apiVersion}/routes`);
}
Is there any way to include the router by require-ing its filepath dynamically?
Thanks for any help.
EDIT 1
If I return my function call as I originally had it:
function getRouter(req, res, next) {
console.log(req.params.apiVersion);
return require(`../${req.params.apiVersion}/routes`);
}
router.use('/:object', getRouter);
I actually get 'v1' written in the console log; so the parameters are there in fact, but the 'return require'... doesn't seem to be working as I imagined.
EDIT 2 - SOLUTION
Thanks to Evert's hints, I eventually got there:
function getRouter(req, res, next) {
const dynamicRouter = require(`../${req.params.apiVersion}/routes`);
return dynamicRouter(req, res, next);
}
router.use('/:object', getRouter);
I'll answer just the first portion of this question.
These two are not equivalent:
// #1
router.use('/:object', require('../v1/routes'));
// #2
function getRouter() {
return require('../v1/routes');
}
router.use('/:object', getRouter);
In the first snippet you are returning the result of require to router.use. In the second you are returning a function that returns the result of getRouter() to router.use.
To make these equivalent, the second one has to look like this:
router.use('/:object', getRouter());
Now we're calling getRouter instead of just passing it.
Overall I think this pattern is really strange though. Especially since you are creating another router in v1/routes. I don't think the top-level router does anything here. Just define this is a general middleware instead.
But, this should at least explain the difference between the first 2 calls.
This question has been asked a million times already, but I never really understood the answers and when I did, they did not suit my specific needs.
I currently have flash messages implemented using this:
app.use(function(req, res, next){
res.locals.sessionFlash = req.session.sessionFlash;
delete req.session.sessionFlash;
next();
});
And when I want to use the message in a route, I have to explicitly recall it like this:
res.render('...', {
sessionFlash: res.locals.sessionFlash
});
Is there a simpler way to do this (preferably one where I would not need to pull the messages from the session manually)?
This behavior is used by big frameworks such as sails.js
you can see code here: https://github.com/balderdashy/sails/blob/0506f0681590dc92986985bc39609c88b718a997/lib/router/res.js
you can achieve it in various ways, but these are the simplest.
1: overriding
you can override the function through "middlewares"
app.use( function( req, res, next ) {
// grab reference of render
var _render = res.render;
// override logic
res.render = function( view, options, fn ) {
// do some custom logic
_.extend( options, {session: true} );
// continue with original render
_render.call( this, view, options, fn );
}
next();
} );
I took this snippet from: https://stackoverflow.com/a/24051339/5384679
you can replicate without lodash etc.
2: wrapping render in a new function
make your own function to wrap the session flashMessage, you can use this to your advantage, i used some es6 syntax out of laziness, but could be replaced easily by es5 code.
res.sendView = function(path, options) {
this.render(path, {
sessionFlash: res.locals.sessionFlash,
...options
});
}
// now you can use res.sendView instead of res.render.
I love generators in nodejs. They help nodejs look more like server-side code. I'm trying to use generators with my Sails app. This is my controller and it works when I visit 'get /hi':
/**
* FooController
*
* #description :: Server-side logic for managing foos
* #help :: See http://sailsjs.org/#!/documentation/concepts/Controllers
*/
module.exports = {
hi: function (req, res){
return res.send("Hi there!");
}
};
However when I change that hi action to a generator function...
module.exports = {
hi: function* (req, res){
return res.send("Hi there!");
}
};
this action never gets to return the response. Like it's waiting for something. How does one utilize ES6 generators within SailsJS controllers and in-fact all of Sails?
You can use it, in fact it'll be great if we all use this style, it adds a lot of readability and flexibility in your code (no more callback-hell), but that is not the way to use it.
As #Cohars stated, sails expect a function as controller actions, you can't pass a generator like in Koa, but that does not prevent you from using them, the thing is that a generator by itself is very useless, you need a function that calls it and iterates it and i believe koa does this for you at framework level, but you have some options available to you at library level, like co or yortus/asyncawait/ which is what i use because node-fibers implemented as functions are way more flexible than es6 generators (though they are almost the same) and i'll show you why in a sec.
So here is how you use it:
First npm install co and require it in your controller, or add it as a global in config/bootstrap.js since you will be using it a lot. Once you are done with it, you can use it in your controllers.
module.exports = {
hi: function(req, res){
co(function* (){
// And you can use it with your models calls like this
let user = yield User.findOne(req.param('id'))
return res.send("Hi there" + user.name + "!");
})
}
};
That's it
Now if you rather use async/await, its pretty similar, it goes like this:
module.exports = {
hi: function(req, res){
async(function(){
// Since await is a function, you can access properties
// of the returned values like this which is nice to
// access object properties or array indices
let userName = await(User.findOne(req.param('id'))).name
return res.send("Hi there" + userName + "!");
})();
}
};
There is a caveat though, if you call other methods of you controller via this, remember that they will refer to the generator fn or the passed regular fn if you use async/await, so be sure to save its context, or since you are already using es6 syntax, you can use fat arrows with async/await, unfortunately not with co, since there is not fat arrow version of generators (..yet?)
So it'll be like this:
module.exports = {
hi: function(req, res){
async(() => {
let params = this._parseParams(req);
let userName = await(User.findOne(params.id)).name
return res.send("Hi there" + userName + "!");
})();
},
_parseParams: function(req){
let params = req.allParams()
// Do some parsing here...
return params
}
};
I've been using the second method for months now, and works perfectly, i've tried with co too and works as well, i just liked more the async/await module (and is supposed to be a little bit faster) and its a perfect match if you are using coffeescript since your sintax will be like do async => await User.find()
UPDATE:
I've created a wrapper that you can use if you use yortus async/await or check the source code and modify it to work with co or something else if you wish.
Here is the link https://www.npmjs.com/package/async-handler, is in alpha but i'm using on my own apps and works as expected, if not submit an issue.
With that the las example would look like this:
module.exports = {
hi: asyncHandler((req, res)->{
let params = this._parseParams(req);
let userName = await(User.findOne(params.id)).name
return res.send("Hi there" + userName + "!");
}),
_parseParams: function(req){
let params = req.allParams()
// Do some parsing here...
return params
}
};
With this handler you get the extra benefit of having async/promise errors to propagate correctly on sails if they are not caught by a try/catch
Sails expects a regular function here, not a generator. Maybe you could take a look at co, not sure it would really help with Sails though. If you really want to use generators, you should probably try Koa, which has several frameworks based on it
The way I am doing it is like this:
const Promise = require('bluebird');
module.exports = {
hi: Promise.coroutine(function* (req, res) {
let id = req.params('id'),
userName;
try {
userName = yield User.findOne(id).name;
}
catch (e) {
sails.log.error(e);
return res.serverError(e.message);
}
return res.ok(`Hi there ${userName}!`);
})
}
Works great. You just need to ensure any functions you call from your controllers return promises.
Put this code in your bootstrap.js, and every thing work like a charm!
var _ = require('lodash');
var coExpress = require('co-express');
sails.modules.loadControllers(function (err, modules) {
if (err) {
return callback(err);
}
sails.controllers = _.merge(sails.controllers, modules);
// hacking every action of all controllers
_.each(sails.controllers, function(controller, controllerId) {
_.each(controller, function(action, actionId) {
actionId = actionId.toLowerCase();
console.log('hacking route:', controllerId, actionId);
// co.wrap,generator => callback
action = coExpress(action);
sails.hooks.controllers.middleware[controllerId][actionId] = action;
});
});
// reload routes
sails.router.load(function () {
// reload blueprints
sails.hooks.blueprints.initialize(function () {
sails.hooks.blueprints.extendControllerMiddleware();
sails.hooks.blueprints.bindShadowRoutes();
callback();
});
});
});
I need to way to add some data to app.json and app.jsonp responses in express after app.json is called, what is the correct way to add middleware to this? I know I could probably do something like:
var jsonTemp = function(status, data) {
if (!data) {
data = status;
status = 200;
}
data.extraValue = 'foo';
res.json(status, data);
}
res.json = jsonTemp;
but monkey patching like that seems like a "bad idea". Is there an official way to hook into the response with some kind of middleware?
I think that monkey patching actually might be the best solution without knowing more about what problem you are trying to solve. The code you showed does however crash the server so don't use that.
app.use(function (req, res, next) {
var orig = res.json;
res.json = function json(status, data) {
if (!data) {
data = status;
status = 200;
}
data.extraValue = 'foo';
orig.call(this, status, data);
};
next();
});
Here I'm storing the original function in the variable orig and then delegating to that one when I'm done with the modification. Your code called your modified function over and over again, since that one now lives under res.json.
You can register your own template engine to format your output. Maybe that helps.
At now I do extending of request object via middleware:
function(req, res, next) {
res.customMethod = function() {
}
next();
}
But I think it's not correct due polluting of res.prototype namespace. Does anybody know better approach or maybe expressjs4 already have any approach for this?
You are almost perfect in your approach but as you said is better to avoid polluting the res namespace: to reach your goal you can use the res.locals property which is designed for this reason. What follows is a snippet where I attach to the response object the translator of my application.
app.use(function(req, res, next){
function Translator (lang) {
this.lang = lang ? lang : 'it';
this.translate = function (sentence) {
var d = dictionaries.get;
var translation = d[this.lang] ? d[this.lang][sentence] : d['it'][sentence];
return translation ? translation : sentence;
};
this.setLang = function (l) {
this.lang = l;
};
};
res.locals.translator = new Translator();
next();
});