I'm new to Express.
I am trying to route a request according to a value in DB. To do this I am calling a service function.
Routing is working successfully but I am loosing the request object.
I have tried to pass the req to the service but it didn't work.
Any help showing how to do this would be nice.
Here is my code block.
var companyService = require("services/companyService");
router.use('/', function (req, res, next) {
companyService.isCompanyOnline(req.body.companyCode).then(function (company) {
if (company) {
router.use('/', require("api/controllers/online"));
}
else {
router.use('/', require("api/controllers/offline"));
}
});
next();
});
module.exports = router;
Services.companyService:
function isCompanyOnline(code) {
var deferred = Q.defer();
companies.findOne({ companyCode: code }, function (err, company) {
if (err) deferred.reject(err.name + ': ' + err.message);
//if (err) throw err;
if (company) {
// return company online parameter
deferred.resolve(company.isOnline);
} else {
// company not found
deferred.resolve();
}
});
return deferred.promise;
}
You are losing the request object because you are not passing it anywhere.
I believe your main issue here is the fact that you have three route handlers registering on the same path /. If I am not mistaken they will be all called in-order that you add them. But the order in your depends on the if you are placing the router.use() calls. So it's unpredictable, and likely not going to work as you expect. Once they are registered they stay in the middleware stack until you restart the app.
I suggest you refactor to have the online/offline logic in one place, or register all your middlewares at once, so you know in which order they get called, and use next() accordingly.
On another note, if you want to pass an argument to required modules, do this:
Change api/controllers/online and the other, to accept an argument, and return the handler function that you are passing.
// your code in api/controllers/online and offline
module.exports = function (req) {
// now you have req available in-scope here
function yourFunctionThatNeedsReq (req) {
// do stuff with req
}
return yourFunctionThatNeedsReq;
};
Then update your require like so. Note the …(req).
router.use('/', require("api/controllers/online")(req) );
My solution:
First, define routes:
app.use('/api/online', require("api/controllers/online"));
app.use('/api/offline', require("api/controllers/offline"));
Then change the url in the router :
if (req.body.companyInfo.isOnline) {
req.url = '/online' + req.url + '/' + req.body.companyInfo.companyPath;
next();
}
else {
req.url = '/offline' + req.url + '/' + req.body.companyInfo.companyPath;
next();
}
Related
I am trying to add a unique ID to my req.session but when the function executes if I want to go to another page it timeout due to infinite loading. Is there a way to do this correctly?
app.use(function (req, res, next) {
if (req.query.inv) {
sql.query(`SELECT * FROM inv WHERE inv='${req.query.inv}';`, (error, result) => {
if(error) console.log(error);
if(result.length < 1) {
req.session.inv= '';
next()
} else {
req.session.inv = `?inv=${req.query.inv}`;
console.log(req.session.inv);
next()
}
});
} else {
if(!req.session.inv) {
req.session.inv= '';
next()
}
}
});
You have a middleware which must call next() when complete so that the next middleware in the stack can be called. See Express's Using middleware documentation.
Take a look at your logic - if inv is not in your query string but does exist in your session, then next() is never called. This aligns with the issue you are having - you add inv to your session, and then on your next page load you will be forever stuck in your middleware. You would instead want logic like this:
app.use(function (req, res, next) {
if (req.query.inv) {
/* .... */
} else if (!req.session.inv) {
req.session.inv= '';
}
next(); // this makes sure that next() always gets called
});
You also have a glaring SQL Injection risk because you are taking a raw query string value and passing it directly into your query. What you want is a parameterized query - I linked to node-postgres documentation even though I'm not sure what database you are actually using. As of now your query is not safe.
I am trying to add cache functionality to the nodejs.
I want to have code like this,
app.get('/basement/:id', cache, (req,res) => {
client.set('basement' + req.params.id,'hello:'+req.params.id)
res.send('success from source');
});
function cache(req,res,next) {
console.log('Inside mycache:' + req.params.id);
client.get('basement' + req.params.id, function (error, result) {
if (error) {
console.log(error);
throw error;
} else {
if(result !== null && result !== '') {
console.log('IN Cache, fetching from cache and returning it');
console.log('Result:' + result);
res.send('success from cache');
} else {
console.log('Not in Cache, so trying to fetch from source ');;
next();
}
}
});
}
I want to apply a middleware function named cache to the request received by the /basement/:id route.
The cache function will receive the key as its parameter.
Inside the cache I want to check for existence of cache and if so return it from there, otherwise I want to call the actual route handler. The key is based on one or more request parameters.
In this way, i will end up writing a separate cache function for every handler in my app.
The logic inside my cache function is generic expect for the key, which is based on the actual request object and may differ from method to method.
So, I want to have a generic cache function which can take the key as a parameter, so that I can have code like this,
app.get('/basement/:id', cache, (req,res) => {
client.set('basement' + req.params.id,'hello:'+req.params.id)
res.send('sucess from source');
});
I mean i will pass the cache key to the cache function. So, the cache function can be generic.
But, if I change my cache function like below as so as to receive the cache key, it does not work.
function cache(cachekey,req,res,next) {
}
Seems like I cannot have another parameter in my cache function to receive the passed parameter.
I would like to pass the cache key as a parameter to the function.
If someone has faced a similar issue, can you please help me on this.
But, if I change my cache function like below as so as to receive the
cache key, it does not work.
You can't because it's not a valid express middleware (It's actually an error middleware), express will pass: req, res, next in that order. and err, req, res, next for error middlewares.
Your cache function will need to return a middleware instead, so you can pass a key to it.
I wanted to create a generic cache function which can take any cache
key. In this case it was id, but other cases may have different keys
function cache(key, prefix = '') {
// Or arrange the parameters as you wish to suits your needs
// The important thing here is to return an express middleware
const cacheKey = prefix + req.params[key];
return (req, res, next) => {
console.log('Inside mycache:' + cacheKey);
client.get(cacheKey , function(error, result) {
if (error) {
console.log(error);
return next(error); // Must be an error object
}
if (result !== null && result !== '') {
console.log('IN Cache, fetching from cache and returning it');
console.log('Result:' + result);
return res.send('success from cache');
}
console.log('Not in Cache, so trying to fetch from source ');;
next();
});
}
}
And now you can use it like this:
app.get('/basement/:id', cache('id', 'basement'), (req, res) => { /* ... */ });
app.get('/other/:foo', cache('foo', 'other'), (req, res) => { /* ... */ });
I have the following express server set up (server is just express() from another file). I am sure there is a way to simplify this to only one server.get() but I haven't been able to figure out how. Any help or points in the right direction would be appreciated.
module.exports.api = function (server, fs) {
server.get('/api/getData/:uuid', function (req, res) {
fs.readFile(__dirname + '/data.json', function (err, data) {
if (err) throw err;
data = JSON.parse(data);
data.forEach(function (match) {
match['uuid'] = match['x'] + '-' + match['y'];
});
var match = data.filter(function (e) {
return e.uuid == req.params.uuid
})[0];
res.send(200, match);
});
});
server.get('/api/getData', function (req, res) {
fs.readFile(__dirname + '/data.json', function (err, data) {
if (err) throw err;
data = JSON.parse(data);
data.forEach(function (match) {
match['uuid'] = match['x'] + '-' + match['y'];
});
res.send(200, data);
});
});
};
Here's a solution that just moves the common code into a shared function, yet still uses the two routes for routing clarity:
function getData(res, uuid) {
fs.readFile(path.join(__dirname, 'data.json'), function (err, fileData) {
if (err) {
return res.send(500);
}
let data = JSON.parse(fileData);
data.forEach(function(match) {
match['uuid'] = match['x'] + '-' + match['y'];
});
if (uuid) {
var match = data.filter(function (e) {
return e.uuid == uuid;
})[0];
}
res.send(200, match);
});
}
module.exports.api = function (server, fs) {
server.get('/api/getData/:uuid', function (req, res) {
getData(res, req.params.uuid);
});
server.get('/api/getData', function (req, res) {
getData(res);
});
};
This changes the following things:
Puts shared code into getData() function that is called from both routes.
Sends an error response if fs.readFile() has an error
Creates new local variable so it doesn't assign back to a function argument which is now a less desirable practice because it prevents some interpreter optimizations.
Uses path.join() to join parts of a path in a more cross platform way.
FYI, unless the data in data.json actually changes from time to time, you could just read this data into a variable once and then cache it rather than rereading it on every one of these requests.
Note: You could use routing wildcards and reduce your code to a single route, but this is mostly considered an anti-pattern because wildcards often match much more than you want, creating situations where you have manually trigger 404 errors for things you didn't intend to match that ended up matching your routing wildcard. So, it is considered a good thing to explicitly declare the routes you intend to match rather and just share the appropriate implementation code rather than trying to collapse things down to a single route that matches more than one form of URL.
There are, of course, always exceptions by remember that the goal is clear, correct, maintainable, reliable code, not necessarily the fewest number of routes.
If you just want to cache the data.json data at server start up time, you can use require() to load and parse it for you like this and then there's really no reason for the sharef fucntion:
const cacheData = require('./data.json');
cacheData.forEach(function(match) {
match['uuid'] = match['x'] + '-' + match['y'];
});
module.exports.api = function (server, fs) {
server.get('/api/getData/:uuid', function (req, res) {
let match = cacheData.filter(function (e) {
return e.uuid == req.params.uid;
})[0];
res.send(match);
});
server.get('/api/getData', function (req, res) {
res.send(cacheData);
});
};
and thanks to be there.
Issue :
I'm making a tiny mongoose "middleware" to handle a mongoose error :
// callback function called at each mongoDB response
var handleDbRes = function(callback) {
return function (err, entries) {
if (err) {
err.status = 500;
return next(err);
}
return callback(entries) // that line throw the exception
}
};
And so I'm using it into an api endpoint, e.g. :
someRouter.get('/', function(req, res) {
models.article.find(handleDbRes(res.json))
})
With that code, I encounter an error :
TypeError: Cannot call method 'get' of undefined
I followed the exception and looked at res.json() declaration, when debugging, I figured out :
var app = this.app;
var *** = app.get('***') // that line throw the exception
I guess that app is not defined cause app doesn't exists in "this".
Please can you help me to solve this problem ? I think that the reason is simple but I don't get it...
Thanks you for listening ;)
EDIT : I tried to res.json.bind(res) and it worked, as I thought, but that's really awful to bind this way for most api endpoint and I guess there is another way to do that kind of functionality without that.
EDIT : Thanks to Mscdex advices, I modified my code this way :
.get('/', function(req, res, next) {
models.article.find(handleDbRes(res.json.bind(res), next))
...
...
// callback function called at each mongoDB response
var handleDbRes = function(successCallback, errorCallback) {
return function (err, entries) {
if (err) {
err.status = 500;
return errorCallback(err);
}
return successCallback(entries)
}
};
When you pass res.json, the context for the json() function is lost (it no longer knows what this is because it is not bound). So here are a few possible solutions:
Use a bound version of the function so that this inside json() will always evaluate correctly:
someRouter.get('/', function(req, res) {
models.article.find(handleDbRes(res.json.bind(res)))
})
Or use a wrapper function instead:
someRouter.get('/', function(req, res) {
function respondJSON(val) {
res.json(val);
}
models.article.find(handleDbRes(respondJSON))
})
Or just pass in res and call res.json() inside handleDbRes():
someRouter.get('/', function(req, res) {
models.article.find(handleDbRes(res))
})
// callback function called at each mongoDB response
var handleDbRes = function(res) {
return function(err, entries) {
if (err) {
err.status = 500;
return next(err);
}
res.json(entries);
}
};
The other problem is that handleDbRes() doesn't have access to next, so you need to also pass that function in for when you run into an error.
In node.js, i have function that before any action is checking if everything is ok at the start of request (validating JSON etc). Almost everything is working ok, but I have one problem. I don't now how to pass object reference using next();
To call checking function I'm using.
app.all('/:action', frontendProcessor.checkSession());
At the middle of this code, I'm using next()
frontendProcessor.checkSession = function(){
return function(req, res, next) {
var inputJson = req.body.JSONVAR || false,
action = req.params.action;
// validation
frontendProcessor.validateJSON(inputJson, afterValidation);
function afterValidation(err, inputData){
if(err) {
global.consoleLog(err);
res.send(fail);
}else{
if(action == 'login' ){
console.log(inputData);
next(inputData); //<< here, how to pass inputData thru next
}else{
mUsers.checkSessionId(email, sessionId, process);
};
};
};
function process(response) {
if(!response){
global.consoleLog("Security Error: Bad session Id.");
var response = JSON.stringify(badSession);
res.send(response);
}else{
global.consoleLog('Security: session ok! next');
next(inputData);
};
};
};
};
next() shouldn't ever pass data because it's just designed to call the next request handler, not a specific request handler. Nirk's comment is correct, you should attach your data to the req object and read it from there when needed.