ExpressJS .param('term', ...) triggers when param not in route URL - node.js

I have two routes in an application:
app.post('/thing/add', thing.addHandler);
app.post('/thing/:thingId/edit', thing.editHandler);
(Order as listed)
The edit handler has a :thingId parameter that I find and populate using the following (simplified for example):
app.param('thingId', function ( req, res, next, id ) {
Thing.findById(id, function ( err, foundThing ) {
...
req.thing = foundThing;
next();
});
});
The .param('thingId') fires when I post to either route.
Any idea why? I thought perhaps because the word thing is a substring of thingId and might be getting matched.

If I use regex for the term:
app.param(/^thingId$/, function ( req, res, next, id ) {..}
it works as I had intended it to.

Express sees your post to /thing/add and thinks that thingId now has the value 'add'. It doesn't know that thingId is supposed to be an integer value.
To fix this, figure out how to make the routes unambiguously different.

Recomend use PUT method for update or modify documents.
I think you need some like this, bro:
app.post('/thing', function(req,res,next){
new Thing();
return next();
});
app.put('/thing/:id', function(req,res,next){
var thingId = req.params.id
Thing.findById(id, function ( err, foundThing ) {
req.thing = foundThing;
next();
});
});

Related

Express Routing - how to require a file containing routes based on a URL Parameter?

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.

Why Array.forEach() is not blocking the middleware?

I know that Array.forEach is synchronous.
But I've this code:
function middleware (req, res, next) {
Array.forEach(
Array.forEach(
if(true) { return next(); }
)
)
return res.send("false");
}
// next()
function isTrue (req, res) {
return res.send("true");
}
And every time I execute it, it sends two headers. First the isTrue function header, and later, the middleware function header.
I understand that if Array.forEach() is synchronous, it should be executed before res.send("false"). But it's not the behaviour. So there is something that I don't understand.
To answer the complete question we'd need to see your code, but to answer the very basic javascript question, the return inside forEach only returns from forEach and not from your outer middleware function. So everything within the middleware function is still executing after forEach.
Again, it's impossible to offer a real solution, but instead of forEach you'd typically use some for these kind of checks.
const ret = [1,2,3].some(e => e === '<yourCondition>');
res.send(ret);

How to setup multi-choice route in express app?

I have defined a route in my express app as such :
app.get('/ts/:space/:mode/:param1/:param2/:fromdate/:todate',(req,res,next) =>{...})
But now I would want to add extra parameters if the :space parameter is equal to a specific value. For example, if :space is blah then the route should transform into
app.get('/ts/:space/:mode/:param1/:param2/:param3/:fromdate/:todate',(req,res,next) =>{...})
Is that possible without hard-coding the blah keyword and putting the hard-coded path before the generic one ?
The addition of extra parameter renders it as a new distinct route, so I think performing a redirect to that new route whenever you encounter :space = blah should satisfy your requirement.
res.redirect()
Added code as per OP request
app.get('/ts/:space/:mode/:param1/:param2/:fromdate/:todate', (req, res, next) => {
let param3 = 'sample';
if (req.params.space === 'blaah') {
res.redirect(`/ts/${req.params.space}/${req.params.mode}/${req.params.param1}/${req.params.param2}/${param3}/${req.params.fromdate}/${req.params.todate}`);
}
});
Split your handler functions to isolate functions.
app.get('/ts/:space/:mode/:param1/:param2/:fromdate/:todate', handler1)
app.get('/ts/:space/:mode/:param1/:param2/:param3/:fromdate/:todate', handler2)
and trick to handler1 special case:
const handler1 = (req,res,next) => {
if (req.params.space === "blah") {
// use hanler for `/ts/:space/:mode/:param1/:param2/:param3/:fromdate/:todate` route
// force set param3 value,
req.params['param3'] = "maybe_a_default_value";
return handler2(req,res,next);
}
// handler for `/ts/:space/:mode/:param1/:param2/:fromdate/:todate` route
}
const handler2 = (req,res,next) => {
// handler for `/ts/:space/:mode/:param1/:param2/:param3/:fromdate/:todate`
// now, req.params.space === "blah" and req.params.param3 === "maybe_a_default_value"
}

How to create flash messages in Express/NodeJS?

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.

Remove route mappings in NodeJS Express

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.

Resources