How to pass req, res to callBack function? - javascript

In node.js, how to pass req, res for callBack function?
for example,
router.get("/", function (req, res) {
var content = '';
fs.readFile("./json/hello.json", function(err, file)
{
if(err)
res.render("index", {
json: content
});
else
{
content = JSON.parse(file);
res.render("index", {
json: content.name
});
}
});
});
It works well. But the code is hard to look because of lot of indentation. So I want to do like this.
router.get("/", function (req, res) {
fs.readFile("./json/hello.json", root_readFileCallBack());
});
function root_readFileCallBack(err, file) {
if (err) {
res.render("index", {
json: content
});
}
else {
content = JSON.parse(file);
res.render("index", {
json: content.name
});
}
}
Code above is better to read. But this makes error that cannot find "render" from "res" variable.
I tried to pass req, res as parameter but it doesn't work well.
How can I pass req, res to callBack fuction?

Create a closure function, the function will return a callback function for readFile function, and the function's param is res object.
router.get("/", function (req, res) {
fs.readFile("./json/hello.json", root_readFileCallBack(res));
});
function root_readFileCallBack(res) {
return function (err, file) {
if (err) {
res.render("index", {
json: content
});
}
else {
content = JSON.parse(file);
res.render("index", {
json: content.name
});
}
}
}

#hoangdv has a great answer that's commonly used in practice. Creating factory functions like that is a useful trick to learn.
Here's another way to go about achieving what you want.
router.get("/", function (req, res) {
const callback = (err, file) => root_readFileCallBack(err, file, res)
fs.readFile("./json/hello.json", callback);
});
function root_readFileCallBack(err, file, res) {
if (err) {
res.render("index", {
json: content
});
}
else {
content = JSON.parse(file);
res.render("index", {
json: content.name
});
}
}
Basically we make root_readFileCallBack() take a res parameter, then in router.get() we wrap root_readFileCallBack to modify its behavior a bit - specifically, we'll cause res to get passed in automatically whenever our new callback is called.
This is using an arrow function, but a normal function would work just fine too.

Append req/res arguments to the callback function.
router.get("/", function (req, res) {
fs.readFile("./json/hello.json", root_readFileCallBack.bind(this, req, res));
});
function root_readFileCallBack(req, res, err, file) {
if (err) {
res.render("index", {
json: content
});
}
else {
content = JSON.parse(file);
res.render("index", {
json: content.name
});
}
}

Related

How to create a reusable code for passport.authenticate?

I have multiple controllers and each controller has multiple methods. In each method I authenticate the user and use the user id returned from the authentication to get the data from database. I am trying to create reusable code for authentication since the code is repeated.
In the controller:
const authenticate = require('../utils/user-authenticate');
exports.getData = async (req, res, next) => {
const userId = await authenticate.user(req, res, next);
console.log(userId);
};
And in the authentication I have:
exports.user = (req, res, next) => passport.authenticate('jwt', async (error, result) => {
if (error) {
// Send response using res.status(401);
} else {
return result;
}
})(req, res, next);
The console.log(userId); prints undefined always. This is print before passport finishes. Looks like async/await does not work the way I want here.
It works if I use await authenticate.user(req, res, next).then() but isn't it possible to assign the result directly to userId variable?
If I use return next('1'): first time undefined but second time it prints 1.
wrapped into a promise:
exports.user = (req, res, next) => new Promise((resolve, reject) => {
passport.authenticate('jwt', async (error, result) => {
if (error) {
// reject(error)
// Send response using res.status(401);
} else {
resolve(result);
}
})(req, res, next);
})
but think about:
//app.use or something similar
addMiddleware(authJWT);
// later in the chain
useMiddleware((req, res, next)=>{
// test auth or end chain
if(!req.JWT_user) return;
req.customField = 'one for the chain'
// process next middleware
next()
});
Thanks #Estradiaz for the suggestion:
exports.user returns undefined ... Return is scoped within inner
callback - if you want to pass it outside wrap it into a promise
Reusable passport.authenticate:
exports.user = (req, res) => {
return new Promise(resolve => {
passport.authenticate('jwt', null, async (error, result) => {
if (error) {
email.sendError(res, error, null);
} else if (result) {
resolve(result);
} else {
return res.status(401).json({errors: responses['1']});
}
})(req, res);
});
};
And this is how I use it in my controller, for instance in a function:
exports.getData = async (req, res, next) => {
const userId = await authenticate.user(req, res);
};

How to display data as JSON using Routes and Controller?

I want to keep routes separate from controller.
My route is:
'use strict';
module.exports = function(app) {
var controller = require('../controllers/controller');
app.route('/').get(controller.index);
};
And controller is:
exports.index = function() {
request = new Request(
"MYQUERY",
function(err, rowCount) {
if (err) {
console.log(err);
} else {
console.log(rowCount + ' rows');
}
connection.close();
}
);
request.on('row', function(columns) {
columns.forEach(function(column) {
if (column.value === null) {
console.log('NULL');
} else {
console.log(column.value);
}
});
});
connection.execSql(request);
};
I am able to see the result in the terminal console but I want to return it as JSON to http. I can use the following if I am using controller and routes all together:
router.get('/about', function (req, res) {
res.send('About this wiki');
})
The callback function to .get (or any router request handler) takes at least two arguments: request and response. You can see this with your example:
router.get('/about', function (req, res) {
res.send('About this wiki');
})
You could rewrite this to make the callback a named function rather than an anonymous function:
const aboutHandler = function (req, res) {
res.send('About this wiki');
});
router.get('/about', aboutHandler);
Your controller.index is the same kind of function, so it will take those two arguments. You just have to change your function to take them:
exports.index = function (req, res) {
This will give you access to res, and you can use it as you need to do send the response via res.send or res.json if you build a JSON object by accumulating the row results. You can use request.on('end' ... to know when the query has emitted all its results.
I might be misunderstanding your question, but do you mean res.json(...);?

Using a module.export function inside the same file same file where it's implemented

I have a controller with two functions:
module.exports.getQuestionnaire = function(application, req, res) {
}
module.exports.getClientDetails = function(application, req, res) {
}
I want to call the getQuestionnaire function inside the getClientDetails function.
Just calling getQuestionnaire() does not work. How should I do this?
What I usually do:
const getQuestionnaire = () => {
//getClientDetails()
}
const getClientDetails = () => {
//getQuestionnaire()
}
module.exports = {getQuestionnaire, getClientDetails}
Define each one as a separate function and then export the functions. Then you can also use the functions on the page
function getQuestionnaire(application, req, res) { }
function getClientDetails (application, req, res) { }
module.exports = {getQuestionnaire, getClientDetails}
you can do like this:
function getQuestionnaire(application, req, res) {
//function body
}
function getClientDetails(application, req, res) {
getQestionnaire(app,req,res)
}
module.exports= {getQestionnaire,getClientDetails}

Error: Can't set headers after they are sent in express-fileupload

app.post('/profile', function(req, res) {
// save file
if (req.files) {
let sampleFile = req.files.sampleFile;
sampleFile.mv('/somewhere/on/your/server/filename.jpg', function(err) {
if (err)
return res.status(500).json(err);
});
}
// do some other stuff
// .............
res.status(200).json(result);
});
I know the problem is caused by return res.status(500).json(err). I can solve the problem by moving res.status(200).json(result) after if (err) block. Since upload file is optional, the user may posting other data without any uploading files. My question is how to send status 200 with a json result after processed other stuff if the solution is
if (err)
return res.status(500).json(err);
res.status(200).json(result);
As was pointed above the problem is you are sending the success reponse outside of the callback.
The solution is to do "other stuff" within the callback.
This should fix the issue -
app.post('/profile', function(req, res) {
// save file
if (req.files) {
let sampleFile = req.files.sampleFile;
sampleFile.mv('/somewhere/on/your/server/filename.jpg', function(err) {
if (err) return res.status(500).json(err);
doOtherStuff();
res.status(200).json(result);
});
} else {
doOtherStuff();
res.status(200).json(result);
}
});
// Write a do other stuff function
function doOtherStuff() {
// do stuff
}
EDIT Adding answer with Promises to avoid code repetition.
function moveFile(file, somePlace) {
return new Promise((resolve, reject)=>{
file.mv(somePlace, function(err) {
if (err) return reject(err);
resolve();
});
});
}
app.post('/profile', function(req, res) {
// save file if present
const fileMovePromise = req.files ?
moveFile(req.files.sampleFile, '/somewhere/on/your/server/filename.jpg')
:
Promise.resolve('No file present');
fileMovePromise
.then(() => {
// do other stuff
})
.catch(err => {
res.status(500).json(err);
});
});
You could use a form of middleware to check if the post is uploading files, the act on the file upload before continuing, have a look at middleware with express here: http://expressjs.com/en/guide/using-middleware.html
With middleware you can do your check to see if the file needs uploading and then call next, otherwise just call next.
app.use('/profile', function (req, res, next) {
if (req.files) {
let sampleFile = req.files.sampleFile;
sampleFile.mv('/somewhere/on/your/server/filename.jpg', function(err, data) {
if (err) {
res.status(500).json(err);
} else {
req.data = data
next()
}
});
}
res.status(200);
}, function (req, res) {
res.status(500).json(req.data);
})
If you are using Node for express, the res param contains res.headersSent, which you can check if the headers have already been sent.
if(res.headersSent) {}
You can also find out a lot more here: https://nodejs.org/api/http.html#http_response_headerssent

How to scope data in express / node controller

I'm trying to avoid callback hell by breaking down my Express / Kraken.js controller into smaller callback functions.
I was processing a request and had about 6 levels of nested anonymous callback functions.
so now I have my main function that looks like this:
// ugh, I know this isn't right
var globalProducts = {};
module.exports = function (server) {
server.get('/project', function (req, res) {
var data = req.query;
globalData = data;
if(!data.projectId || !data.ruleSetId)
res.json({error: "Incomplete input data."});
// pass response to products call back
Project.findOne({ _id: mongoose.Types.ObjectId(data.projectId) }, setUpProducts);
});
};
function setUpProducts(err, project){
// get all products and pass them down the pipe
project.findAllChildren(setUpRules);
}
function setUpRules(err, products) {
// we need to access products in another function
globalProducts = products;
// find the rule set and build the rule Flow
RuleSet.findOne({ _id: mongoose.Types.ObjectId(globalData.ruleSetId) }, function(err, ruleSet) {
ruleSet.buildFlow(processFlow);
});
}
My question is what is the best way to pass around info between callbacks ? My solution was var globalProducts = {}; but to me, the controller contain any 'global state' .. whats the best way to handle this ?
Doing this is a bad idea. It will cause race condition type issue — basically it's the same as sharing data in multithreaded environment. Instead you can use req or res to store data. To do that you need them in scope, so you can define all functions inside route handler or make each function a middleware so it will have req and res as arguments. Here is an example of this approach:
function check (req, res, next) {
if(!req.query.projectId || !req.query.ruleSetId) return res.json({error: "Incomplete input data."});
next()
}
function findProject (req, res, next) {
Project.findOne({ _id: mongoose.Types.ObjectId(req.query.projectId) }, after);
function after (err, project) {
if (err) return next(err);
req.project = project;
next();
}
}
function findProducts (req, res, next) {
req.project.findAllChildren(after)
function after (err, products) {
if (err) return next(err);
req.products = products;
next();
}
}
function respond (req, res) {
res.render('view', {
products : req.products,
project : req.project
});
}
module.exports = function (server) {
server.get('/project', check, findProject, findProducts, respond);
};

Categories

Resources