Node.js Error: Can't set headers after they are sent - javascript

I know this question already exists but the solutions I found didn't work for me. I am building a basic create function in Node.js. It first checks if the object already exists and if it doesn't, creates one. And I am getting this error even after I added else if and return to every condition. But it seems everything get executed regardless. This is my code:
controllers/shop.js:
var Shop = require('../models/shop').model;
module.exports = {
create: function(req, res) {
if(typeof(req) != 'object')
return res.status(400).send({error: Error.InvalidInput});
if(req.body.name === null) return res.status(400).json({error: Error.missingParameter('name')});
Shop.findOne({name: req.body.name}, function(err, shop){
if(err) return res.status(500).json({error: Error.unknownError});
else if (shop) return res.status(409).json({error: Error.alreadyExists('Shop')});
}).exec(Shop.create({name: req.body.name}, function(err, shop) {
if (err) return res.status(500).json({error: Error.unknownError});
else if (shop) return res.status(201).json(shop);
else if (!shop) return res.status(400).json({error: Error.createFailed('Shop')});
}));
},
}

Either you should pass a callback in the find method or use a function with exec but should not use both since they both are asynchronous and invoked at the same time.
You can refactor your code as below.
var Shop = require('../models/shop').model;
module.exports = {
create: function(req, res) {
if(typeof(req) != 'object')
return res.status(400).send({error: Error.InvalidInput});
if(req.body.name === null) return res.status(400).json({error: Error.missingParameter('name')});
Shop.findOne({name: req.body.name}, function(err, shop){
if(err) return res.status(500).json({error: Error.unknownError});
else if (shop) return res.status(409).json({error: Error.alreadyExists('Shop')});
else {
Shop.create({name: req.body.name}, function(err, shop) {
if (err) return res.status(500).json({error: Error.unknownError});
else if (shop) return res.status(201).json(shop);
else if (!shop) return res.status(400).json({error: Error.createFailed('Shop')});
});
}
});
},
}

Try setting variables for the response status and error/other messages in your if statements. then at the end of your create function return a single response object populated with the variables
var Shop = require('../models/shop').model;
module.exports = {
create: function(req, res) {
var status = 200;
var message = "";
if(typeof(req) != 'object')
status = 400;
message = Error.InvalidInput;
...
return res.status(status).send({error: message});
});
}));
},
}

Related

Cannot set headers after they are sent to the client in node js during updating data

I am new to node.js developement and encountered an error.
First of all here the code inside controller.js.
exports.updateCar = async (req, res,next) => {
const {name} = req.fields
try {
await updateCarService(name,next, (data) =>{
if(data == null || data){
res.status(201).json({
status:true,
message:'updated',
data
});
return
}
})
next()
} catch(e) {
res.sendStatus(500) && next(e)
}
};
and here the method inside services.js.
function updateCars(existingCar,callback){
Car.find({name : existingCar.name}, function (err, docs) {
if (docs.length){
//callback('Car exists already',null);
callback(err,docs)
}else{
existingCar.save(function(err,carr){
console.log('-------',carr)
callback(err,carr);
});
}
});
}
const updateCarService = async (name,next,callback) => {
try{
return await Car.find({name : name},(err,existingCar) => {
if (!err && existingCar.length > 0){
existingCar[0].name = 'audi';
updateCars(existingCar[0],(err2,car) =>{
if (err2 || !car){
console.log('error updated car: ',err2);
}else{
console.log('car updated: ',car);
return callback(car)
}
return
});
}
else if (!err && existingCar.length == 0){
return callback(null)
}
})
}
catch(e){
console.log(error)
}
}
After updating data i get error that :
events.js:187
throw er; // Unhandled 'error' event
^
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
I have tried many possible ways to solve this issue but i couldn't got any solution.
Please help me to resolve this issue.Also if any of style of coding is not good then correct me.

Why does router.get('/:x',..) make another function return null, while router.get('/foo/:x',...) is absolutely fine?

After some agonizing weeks of trying to find the core of my problem, I found it. What I want to know is what's the reason for this behaviour.
I built an api for my mongodb server the way I learned in school using Model,Controller,Routes.
In my lobbyRoutes.js it turns out that if I write
router.get('/:lobbyID',controller.get);
Another function:
router.get('/getAvailable',controller.getAvailable)
- will return null when I type in the URL (.../api/lobbies/getAvailable - just returns the word null instead of a json).
Meanwhile if I make this little change:
router.get('/get/:lobbyID',controller.get); (add a 'get/' before ':lobbyID') will fix the problem and getAvailable returns a json as expected and not a null anymore.
lobbyRoutes.js:
var controller = require('./LobbyController');
router.post('/insert',controller.insert);
router.get('/getall',controller.getall);
router.get('/get/:lobbyID',controller.get); //the one that affects
router.post('/update',controller.update);
router.post('/delete',controller.delete);
router.get('/getAvailable',controller.getAvailable); // the one that is affected
router.post('/connect',controller.connect);
router.post('/disconnect',controller.disconnect);
router.get('/getable',controller.getable);
module.exports = router;
and if it helps, lobbyController.js :
exports.get = function(req, res,next){
CurrentLobby.findOne({_id:req.params.lobbyID}).then(function(data){
res.json(data);
},function(err){
next(err);
});
};
exports.getall = function(req, res, next){
CurrentLobby.find( {} ).then(function(data){
res.json(data)
},
function(err){
next(err);
}
);
};
exports.getable = function(req,res,next){
CurrentLobby.findOne({isAvailable:true}).then(function(data){
res.json(data)
},
function(err)
{
next(err);
});
};
exports.insert = function(req, res, next){
var isAvailable = true;
var playerCount = 0;
var playerArray = [];
var PINCODE = -1;
var gameMode = "";
var currentWord = "";
var query = {title:"Index"};
CurrentLobby.findOne({_id:"5d7a9060004a5c3318d0db98"
}).then(function(data){
console.log(data);
var lobby = {title:"Lobby_"+(data.playerCount+1).toString(), isAvailable:isAvailable,playerCount:playerCount,playerArray:playerArray,PINCODE:PINCODE,gameMode:gameMode,currentWord:currentWord};
var newItem = new CurrentLobby(lobby);
newItem.save(function(err, item){
if(err){
next(err);
}
res.json(item);
});//end of save
CurrentLobby.findOne(query).then(function(data){
data.playerCount++;
data.save(function(err,item)
{
if(err) {next(err)};
console.log("index incremented");
});
},
function(err)
{
next(err);
});
},function(err)
{
next(err);
});
};
exports.delete = function(req, res, next){
CurrentPost.remove({_id:req.body._id}).then(function(){
res.send("deleted " + req.body._id);
}, function(err){
next(err);
}
);
} ;
exports.update = function(req, res, next){
CurrentLobby.findOne({_id:req.query.lobbyID}).then(function(data){
data.isAvailable = (typeof req.body.isAvailable == 'undefined') ? data.isAvailable : req.body.isAvailable;
data.playerCount = (typeof req.body.playerCount=='undefined') ? data.playerCount : req.body.playerCount;
data.playerArray = (typeof req.body.playerArray=='undefined') ? data.playerArray : req.body.playerArray;
data.PINCODE = (typeof req.body.PINCODE=='undefined') ? data.PINCODE : req.body.PINCODE;
data.gameMode = (typeof req.body.gameMode=='undefined') ? data.gameMode : req.body.gameMode;
data.currentWord = (typeof req.body.currentWord=='undefined') ? data.currentWord : req.body.currentWord;
data.save(function(err, item){
if(err){
next(err);
}
res.json(item);
});
},function(err){
next(err);
});
};
exports.getAvailable=function(req,res,next)
{
var query = {isAvailable:true};
CurrentLobby.findOne(query).then(function(data){
res.json(data);
},
function(err){
next(err);
}
);
};
exports.connect=function(req,res,next)
{
var query={_id:req.query.lobbyID}
CurrentLobby.findOne(query).then(function(data)
{
console.log("called connect, user:"+req.query.userID+", lobby:"+req.query.lobbyID);
data.isAvailable=false;
if (typeof data.playerArr !== 'undefined' && data.playerArr.length >= 0) {
// the array is defined and has at least one element
data.playerArr.push(req.query.userID);
console.log("successfully added user");
data.playerCount++;
if(data.playerCount==8)
data.isAvailable=false;
}
else{
data.playerArr.push(req.query.userID);
console.log("1st user");
}
data.save(function(err, item){
if(err){
next(err);
}
res.json(item);
});
},
function(err)
{
next(err);
});
};
exports.disconnect=function(req,res,next)
{
var query={_id:req.query.lobbyID}
CurrentLobby.findOne(query).then(function(data)
{
console.log("called disconnect, user:"+req.query.userID+", lobby:"+req.query.lobbyID);
data.isAvailable=false;
if (typeof data.playerArr !== 'undefined' && data.playerArr.length >= 0) {
// the array is defined and has at least one element
var index = data.playerArr.indexOf(req.query.userID);
if (index !== -1){ data.playerArr.splice(index, 1);
console.log("successfully removed user");
data.playerCount--;
//room available
if(data.playerCount==0)
{
data.PINCODE=-1;
data.gameMode="";
data.currentWord="";
data.isAvailable=true;
}
}
else{
console.log("not found");
}
}
else console.log("array is empty");
data.save(function(err, item){
if(err){
next(err);
}
res.json(item);
});
},
function(err)
{
next(err);
});
};
I just would like to know why I can't type a parameter first (even though it doesn't affect the function itself, only the other one). Thanks.
As user 'Mike Pomax Kamermans' said, using parameter first in order will break all of the other routes so the word getAvailable will be recognized as a lobbyID paramater rather than call the function 'getAvailable'. quite embarassed I could not figure it out alone...

Why does express show "error can not set headers after they are sent" in this method?

I have a nodejs app which uses express. When I try to call this method below I get logged in console "Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client".
I found some other threads here about it being the async-methods, but I cant seem to pinpoint where its wrong in my code. I tried console logging, and the last place that logs before the return is just after this try catch. I also added "Return" before sending res.status to all my code, but it still emits this error. The code in the controller is much longer, but no console.logs where made after this piece of code. Any idas what Im doing wrong?
router.post('/add', auth, async (req, res) => {
if(req.body.mobilenumber === ''){
return res.status(400).send('Bad input data, please check input.')
}
const checkUserText = 'SELECT * FROM SQL';
let dbResult1;
try {
dbResult1 = await GetStuff(checkUserText, [req.body.mobilenumber]);
} catch (err) {
logger.error(err)
return res.status(500).send(err)
}
if (dbResult1.rowCount === null || dbResult1.rowCount >= 1) {
return res.sendStatus(405).send('User already exist')
}
var insertUser = 'INSERT INTO SQL RETURNING *';
let dbResult2;
try {
let insData = [paramsFromBody];
dbResult2= await GetStuff(insertUser, insData);
} catch (err) {
logger.error(err)
return res.status(500).send(err)
}
if (dbResult2 === null || dbResult2.rowCount === 0) {
return res.status(500).send('error')
logger.error('Error')
}
return res.status(200).send('Added OK.')
})
async function GetStuff(text, id){
try {
return await db.query(text, id);
} catch(error) {
throw new Error('Failed DB-action' + error);
}
}
From what I see in the code you've shared, this will cause the error you report:
return res.sendStatus(405).send('User already exist')
because .sendStatus() sends the whole response immediately and then .send() tries to send another response. That should be this:
return res.status(405).send('User already exist');
Also, you can simplify GetStuff() to just this:
function GetStuff(text, id) {
return db.query(text, id).catch(err => {
throw new Error('Failed DB-action' + err);
});
}
One clue is that there's pretty much never a reason to do:
return await fn();
You can just do:
return fn();
Both return the same promise with the same resolved value.
Also, note that in this code:
if (dbResult2 === null || dbResult2.rowCount === 0) {
return res.status(500).send('error')
logger.error('Error')
}
The logger.error() will never get called since it's after a return statement.
Your code could be simplified by using one central try/catch and only sending the response in two places (one for success and one for error):
// our own error subclass that holds a status value
class ResponseError extends Error {
constructor(msg, status) {
super(msg);
this.status = status;
}
}
router.post('/add', auth, async (req, res) => {
try {
if (req.body.mobilenumber === '') {
throw new ResponseError('Bad input data, please check input.', 400);
}
const checkUserText = 'SELECT * FROM SQL';
let dbResult1 = await GetStuff(checkUserText, [req.body.mobilenumber]);
if (dbResult1.rowCount === null || dbResult1.rowCount >= 1) {
throw new ResponseError('User already exist', 405);
}
const insertUser = 'INSERT INTO SQL RETURNING *';
let dbResult2 = await GetStuff(insertUser, [paramsFromBody]);
if (dbResult2 === null || dbResult2.rowCount === 0) {
throw new ResponseError("dbResult2 empty", 500);
}
res.send('Added OK.')
} catch(e) {
// if no specific status specified, use 500
let status = e.status || 500;
res.status(status).send(e);
}
})
function GetStuff(text, id) {
return db.query(text, id).catch(err => {
throw new Error('Failed DB-action' + error);
});
}

How to return value from a series of cascading async code

I need some advice on how to re/write the db specific cascading code (callback) so that I can effectively return a value to the underlying if/else.
I am using restify and the db lib is node-mssql (tedious).
function authenticate(req, res, next) {
var auth = req.authorization;
var err;
if (auth.scheme !== 'Basic' || ! auth.basic.username || ! auth.basic.password) {
authFail(res);
err = false;
} else {
var sql = "SELECT ..."
var connection = new mssql.Connection(config.mssql, function(err) {
if (err) {console.log(err);}
var request = connection.request();
request.query(sql, function(err, recordset) {
if (err) {console.log(err);}
if (recordset.length === 0) {
authFail(res);
err = false; // <--- I need to be able to return this
} else {
authSuccess();
}
});
});
}
next(err);
}
I've reviewed the suggested duplicate, and while I think, I understand the issue, I can't work out the cleanest (lets be honest any) way to get this to work.
How about using Promises?
function authenticate(req, res, next) {
var auth = req.authorization;
if (auth.scheme !== 'Basic' || ! auth.basic.username || ! auth.basic.password) {
authFail(res);
next(false);
} else {
var sql = "SELECT ..."
var connection = new mssql.Connection(config.mssql, function(err) {
if (err) {console.log(err);}
var request = connection.request();
request.query(sql).then(function(recordset) {
if (recordset.length === 0) {
authFail(res);
return false; // <--- return this
} else {
authSuccess();
}
}).catch(function(err) {
console.log(err);
return err;
}).then(function(err) { next(err); });
});
}
}

How can I return out of a node callback to mongo and redirect?

I think I'm getting confused with the callback, but I have the following code, and I'm trying to return newpost_template when the subject name is invalid. I think my logic is correct, but it's not returning. It's passing right through. It works perfectly fine and renders newpost_template when there is !title. Any advice is welcome, thanks.
This works:
if (!title) {
var errors = "Post must contain a title";
return res.render("newpost_template", {
subject: title,
username: req.username,
body: req.body,
tags: req.tags,
errors: errors
});
}
This doesn't work:
users.findAllSubjectNames(title, req.username, res, req, function(err, doc) {
"use strict"
if (err) return next(err);
console.log('doc');
console.log(doc);
if (doc === null) {
console.log('this shows');
var errors = "Subject name already taken!";
console.log('this also shows');
//return res.redirect(" / newpost ")
return res.render("newpost_template ", {
subject: title,
username: req.username,
body: req.body,
tags: req.tags,
errors: errors
});
console.log('this doesnt show');
}
});
this.findAllSubjectNames = function(title, user, res, req, callback) {
"use strict";
users.find({}, {
"teacher.subject ": 1
}).toArray(function(err, result) {
"use strict ";
if (err) return callback(err, null);
console.log('result');
for (var r = 0; r < result.length; r++) {
for (var t = 0; t < result[r].teacher.length; t++) {
if (result[r].teacher[t].subject == title && result[r]._id != user) {
console.log('INVALID!');
return callback(err, null);
//return res.redirect(" / newpost ")
}
}
}
return callback(err, result);
});
}
I got it. I had to put the redirect and res.render in an if else clause so only one would execute and only after doc returned. I also had to separate it into it's own function.
users.findAllSubjectNames(title, req.username, function(err, doc){
"use strict"
if(err) return next(err);
//return res.redirect("/newpost") //res.render("newpost_template", {subject:title, username:req.username, body:req.body, tags:req.tags, errors:errors});
if(doc === null){
var errors = "Subject name already taken!";
return res.render("newpost_template", {subject:title, username:req.username, body:req.body.body, tags:req.tags, errors:errors});
} else {
classNumber(title, req, res, next);
return res.redirect("/profile");
}
});

Categories