I'm following a tutorial in the net. It's a MERN project with mongo/mongoose. When I have implemented the update function in the controller the following error has occured :
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
I've seen the answers about similar issue where they say it's because there are 2 or multiple calls of res (res.json(), res.send() etc..), but I don't see where must I change this in the following function :
module.exports.updateUser = async(req, res) => {
if (!ObjectID.isValid(req.params.id))
return res.status(400).send("ID unknown : " + req.params.id);
try {
await UserModel.findOneAndUpdate({
_id: req.params.id
}, {
$set: {
bio: req.body.bio
}
},
(err, docs) => {
if (!err)
return res.send(docs);
if (err)
return res.status(500).send({ message: err });
}
)
} catch (err) {
return res.status(500).json({ message: err });
}
};
It may be that you've mixed up two different error handling patterns.
You don't need try/catch if you're using built in error handling of findOneAndUpdate()
await UserModel.findOneAndUpdate({
_id: req.params.id
}, {
$set: {
bio: req.body.bio
}
},
(err, docs) => {
if (!err)
return res.send(docs);
if (err)
return res.status(500).send({ message: err });
}
)
and if you are using try/catch, you don't need findOneAndUpdate's error handling:
try {
const user = await UserModel.findOneAndUpdate({
_id: req.params.id
}, {
$set: {
bio: req.body.bio
}
})
return res.send(user)
} catch (err) {
return res.status(500).json({ message: err });
}
Could you please change code like this:
module.exports.updateUser = async(req, res) => {
if (!ObjectID.isValid(req.params.id))
return res.status(400).send("ID unknown : " + req.params.id);
try {
const result = await UserModel.findOneAndUpdate({
_id: req.params.id
}, {
$set: {
bio: req.body.bio
}
});
return res.send(result);
} catch (err) {
return res.status(500).json({ message: err });
}
};
I have the following route using express and mongoose that update 2 collections from one route. The updates work and are reflected in MongoDb, however, at the end of the request the server crashes with the error: code: 'ERR_HTTP_HEADERS_SENT'. Is there a way for me to avoid this error?
router.post("/user-adopted", verify, (req, res) => {
const userId = req.body.userId;
const petId = req.body.petId;
User.findOneAndUpdate(
{ _id: userId },
{ adoptedPet: petId, petId: false, adoptionRequest: false },
function (err, result) {
if..else..
}
);
Data.findOneAndUpdate(
{ _id: petId },
{ adopted: true },
function (err, result) {
if...else..
);
});
As soon as the User record is updated it performs the operation you have defined in the callback and Data record is left out so to prevent that do the updation of Data record in the callback of user and try to use updateOne() instead of findOneAndUpdate() :
User.updateOne(
{ _id: userId },
{ adoptedPet: petId, petId: false, adoptionRequest: false },
function (err, result) {
if(err) res.send(err)
Data.updateOne(
{ _id: petId },
{ adopted: true },
function (err, result) {
if(err)
res.send(err)
else{
// Redirect where you want to go
}
}
);
}
);
I cannot remove an element inside of an array that is a property of a MongoDB Model.
Please remember this is a NodeJS module mongooseJS and not the real MongoDB so functionalities are not the same..
GOAL: Delete an object from the statusLiked array. | I have also confirmed that the value of status.id is correct.
Model:
Const userSchema = new mongoose.Schema({
myStatus: Array,
statusLiked: Array,
)};
Delete:
1. Deletes the status(works). 2. Delete the status from User.statusLiked(no work).
exports.deleteStatus = (req, res, next) => {
var CurrentPost = req.body.statusid; // sends in the status.id
Status.remove({ _id: CurrentPost }, (err) => {
if (err) { return next(err); }
// vvvv this vvv
User.update( {id: req.user.id}, { $pullAll: {_id: CurrentPost }, function(err) { console.log('error: '+err) } });
req.flash('success', { msg: 'Status deleted.' });
res.redirect('/');
});
};
What happens: The specific status(object) is deleted from the database. But the status still remains in the User.statusLiked array.
What I want to happen: Status to be deleted from the User.statusLiked array and the status to be deleted from the database. Then, reload the page and display a notification.
I got it to work somehow. Working code:
exports.deleteStatus = (req, res, next) => {
var CurrUser = req.body.userid;
var CurrentPost = req.body.post;
Status.remove({ _id: CurrentPost }, (err) => {
if (err) { return next(err); }
console.log('meeee'+CurrentPost+'user: ' +CurrUser);
req.flash('success', { msg: 'Status deleted.' });
res.redirect('/');
});
User.update(
{ _id: new ObjectId(CurrUser)},
{ $pull: { myStatus : { _id : new ObjectId(CurrentPost) } } },
{ safe: true },
function (err, obj) {
console.log(err || obj);
});
};
I am in a bit of a pickle. Whenever I create a new resume as a logged in user it doesn't add the resume id as an array. I.e, ["20293", "2932392", "32903239"]
Instead, it overwrites the current resume id in the users schema. Here is the code
UserSchema
const UserSchema = new Schema({
_vId: {
type: String,
default: id.generate()
},
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
accountType: {
type: String,
enum: ['Alphaneer', 'Administrator', 'Support', 'PRO'],
default: 'Alphaneer'
},
email: {
type: String,
required: true,
trim: true
},
username: {
type: String,
required: true,
trim: true,
unique: true
},
bio: {
type: String,
default: "No bio provided."
},
// TODO: Hash the password before inserting as a document :)
password: {
type: String,
required: true
},
createdAt: {
type: String,
default: moment(new Date()).format("MMM DD, YYYY") // "Sun, 3PM 17"
},
resume: [ { type: mongoose.Schema.ObjectId, ref: "Resume" } ]
});
Where I post my resume
// POST /dashboard/resume/create
router.post('/resume/create', (req, res, next) => {
Resume.create(req.body, (err, resume) => {
if (err) {
var err = new Error("Error:" + err);
err.status = 404;
next(err);
} else {
req.user = jwtDecode.decode(req.session.tokenID, 'secret');
//I am assuming that you have saved your resume and getting the saved object in `resume`, now update the logged in user in req.user
var user = req.user.sessionId;
var updateData = {
resume: resume._id
}
//save the updated user
User.findByIdAndUpdate(user, updateData, function(err, user) {
console.log(user);
if (err) {
res.json(err);
} else {
res.json(user);
}
})
}
})
});
gif of submitting new resumes
UPDATE:
error picture
UPDATED CODE:
// POST /dashboard/resume/create
router.post('/resume/create', (req, res, next) => {
Resume.create(req.body, (err, resume) => {
if (err) {
var err = new Error("Error:" + err);
err.status = 404;
next(err);
} else {
req.user = jwtDecode.decode(req.session.tokenID, 'secret');
//I am assuming that you have saved your resume and getting the saved object in `resume`, now update the logged in user in req.user
var user = req.user.sessionId;
var updateData = {
resume: resume._id
}
//save the updated user
User.findById(user, function(err, user) {
console.log(user);
if (err) {
res.json(err);
} else {
user.resume.push(resume.id)
user.save(function(user) {
return res.json(user);
});
}
})
}
})
});
This is wrong:
var user = req.user.sessionId;
var updateData = {
resume: resume._id
}
//save the updated user
User.findByIdAndUpdate(user, updateData, function(err, user) {
console.log(user);
if (err) {
res.json(err);
} else {
res.json(user);
}
});
The resume field is an array and you are manipulating it as a string field. The method findOneAndUpdate do two things:
Find the document by it's id
Update it with the new data
The second argument is the new data to set. So, the second step is translated to:
User.upate({ _id: user }, { resume: resume._id });
Can you see what's wrong? resume must store an array of resume's id and your are setting a id as value. Obviously this will throw an MongooseError.
Your second shot is correct but has a typo error:
User.findById(user, function(err, user) {
console.log(user);
if (err) {
res.json(err);
} else {
user.resume.push(resume.id)
user.save(function(user) {
return res.json(user);
});
}
});
You must add the _id field since this is the ObjectID of the new created document (resume). So, you need to do user.resume.push(resume._id) instead.
Update
According with your last comment, you want to populate your User model, that is, through association id's retrieve all model data. In this case, is recommended that the resumes array change like this:
...
resumes: [
{
resume: {
type: Schema.Types.ObjectId,
ref: 'Resume'
}
}
]
To populate the User document with all Resume data you just need to reference the resume key in resumes field array.
User.findById(user, function(err, user) {
if (err) {
return res.json({ success: false, message: err.message });
}
user.resume.push(resume.id)
user.save(function(err, user) {
if (err) {
return res.json({ success: false, message: err.message });
}
// save was fine, finally return the user document populated
User.findById(user).populate('resumes.resume').exec(function(err, u) {
return res.json(u);
});
});
}
});
The populate method accepts a string with the fields that we want fill with it model data. In your case is an only field (resume). After run the query, you will get something like this:
{
_id: a939v0240mf0205jf48ut84sdfdjg4,
...,
resumes: [
resume: {
_id: f940tndfq4ut84jofgh03ut85dg9454g,
title: 'Some title'
},
...
]
}
Just to follow up on my comment regarding how I suggest you solve the issue:
router.post('/resume/create', (req, res, next) => {
Resume.create(req.body, (err, resume) => {
if (err) {
var err = new Error("Error:" + err);
err.status = 404;
next(err);
} else {
req.user = jwtDecode.decode(req.session.tokenID, 'secret');
//Here, instead of creating a new key entry for resume, you rather push new resume-id into the resume property of the "found user".
//find, update and save the user
User.findOne({_id: req.user.sessionId}, function (err, userToUpdate) {
userToUpdate.toJSON().resume.push(resume.id);
userToUpdate.save(function (err) {
if(err) {
console.error('ERROR!');
}
});
});
}
})
});
I left the rest of your code (saving new resume) untouched - I assume that part works. Give this a try and let me know if you encounter some problems.
upsertAsync: function(req, res, next) {
var datas = req.body;
var user = req.body.user;
async.each(datas, function(data, cb) {
sequelize().transaction({
autocommit: true
}, function(t) {
models.Customer.upsert({
id: data.id,
code: data.code,
createdBy: user.name,
modifiedBy: user.name
}, {
transaction: t
}).then(function(customer) {
cb();
}).
catch (function(error) {
t.rollback();
res.status(500).json(error);
});
}, function(err, data) {
log.debug('error (upsertAsync)', err);
if (err) {
t.rollback();
res.status(500).json(err);
}
t.commit();
res.json({
responseCode: '200',
message: 'Customer has been created..!',
data: data
});
});
});
},
I'm using async.each to insert data into sqlite database at the same time. I want to rollback if any error occurs but it shows error which is [TypeError: Cannot set property 'options' of undefined