How to do validations before saving the edited data in mongoose?
For example, if sample.name
already exists in the database, the user will receive a some sort of error, something like that, here's my code below
//Post: /sample/edit
app.post(uri + '/edit', function (req, res, next) {
Sample.findById(req.param('sid'), function (err, sample) {
if (err) {
return next(new Error(err));
}
if (!sample) {
return next(new Error('Invalid reference to sample information'));
}
// basic info
sample.name = req.body.supplier.name;
sample.tin = req.body.supplier.tin;
// contact info
sample.contact.email = req.body.supplier.contact.email;
sample.contact.mobile = req.body.supplier.contact.mobile;
sample.contact.landline = req.body.supplier.contact.landline;
sample.contact.fax = req.body.supplier.contact.fax;
// address info
sample.address.street = req.body.supplier.address.street;
sample.address.city = req.body.supplier.address.city;
sample.address.state = req.body.supplier.address.state;
sample.address.country = req.body.supplier.address.country;
sample.address.zip = req.body.supplier.address.zip;
sample.save(function (err) {
if (err) {
return next(new Error(err));
}
res.redirect(uri + '/view/' + sample._id);
});
});
});
This question is related to
node.js
validation
express
mongoose
In addition to already posted examples, here is another approach using express-async-wrap and asynchronous functions (ES2017).
Router
router.put('/:id/settings/profile', wrap(async function (request, response, next) {
const username = request.body.username
const email = request.body.email
const userWithEmail = await userService.findUserByEmail(email)
if (userWithEmail) {
return response.status(409).send({message: 'Email is already taken.'})
}
const userWithUsername = await userService.findUserByUsername(username)
if (userWithUsername) {
return response.status(409).send({message: 'Username is already taken.'})
}
const user = await userService.updateProfileSettings(userId, username, email)
return response.status(200).json({user: user})
}))
UserService
async function updateProfileSettings (userId, username, email) {
try {
return User.findOneAndUpdate({'_id': userId}, {
$set: {
'username': username,
'auth.email': email
}
}, {new: true})
} catch (error) {
throw new Error(`Unable to update user with id "${userId}".`)
}
}
async function findUserByEmail (email) {
try {
return User.findOne({'auth.email': email.toLowerCase()})
} catch (error) {
throw new Error(`Unable to connect to the database.`)
}
}
async function findUserByUsername (username) {
try {
return User.findOne({'username': username})
} catch (error) {
throw new Error(`Unable to connect to the database.`)
}
}
// other methods
export default {
updateProfileSettings,
findUserByEmail,
findUserByUsername,
}
Resources
There is a more simpler way using the mongoose exists function
router.post("/groups/members", async (ctx) => {
const group_name = ctx.request.body.group_membership.group_name;
const member_name = ctx.request.body.group_membership.group_members;
const GroupMembership = GroupModels.GroupsMembers;
console.log("group_name : ", group_name, "member : ", member_name);
try {
if (
(await GroupMembership.exists({
"group_membership.group_name": group_name,
})) === false
) {
console.log("new function");
const newGroupMembership = await GroupMembership.insertMany({
group_membership: [
{ group_name: group_name, group_members: [member_name] },
],
});
await newGroupMembership.save();
} else {
const UpdateGroupMembership = await GroupMembership.updateOne(
{ "group_membership.group_name": group_name },
{ $push: { "group_membership.$.group_members": member_name } },
);
console.log("update function");
await UpdateGroupMembership.save();
}
ctx.response.status = 201;
ctx.response.message = "A member added to group successfully";
} catch (error) {
ctx.body = {
message: "Some validations failed for Group Member Creation",
error: error.message,
};
console.log(error);
ctx.throw(400, error);
}
});
If you're searching by an unique index, then using UserModel.count may actually be better for you than UserModel.findOne due to it returning the whole document (ie doing a read) instead of returning just an int.
check with one query if email or phoneNumber already exists in DB
let userDB = await UserS.findOne({ $or: [
{ email: payload.email },
{ phoneNumber: payload.phoneNumber }
] })
if (userDB) {
if (payload.email == userDB.email) {
throw new BadRequest({ message: 'E-mail already exists' })
} else if (payload.phoneNumber == userDB.phoneNumber) {
throw new BadRequest({ message: 'phoneNumber already exists' })
}
}
Here is another way to accomplish this in less code.
UPDATE 3: Asynchronous model class statics
Similar to option 2, this allows you to create a function directly linked to the schema, but called from the same file using the model.
model.js
userSchema.statics.updateUser = function(user, cb) {
UserModel.find({name : user.name}).exec(function(err, docs) {
if (docs.length){
cb('Name exists already', null);
} else {
user.save(function(err) {
cb(err,user);
}
}
});
}
Call from file
var User = require('./path/to/model');
User.updateUser(user.name, function(err, user) {
if(err) {
var error = new Error('Already exists!');
error.status = 401;
return next(error);
}
});
Another way to continue with the example @nfreeze used is this validation method:
UserModel.schema.path('name').validate(function (value, res) {
UserModel.findOne({name: value}, 'id', function(err, user) {
if (err) return res(err);
if (user) return res(false);
res(true);
});
}, 'already exists');
For anybody falling on this old solution. There is a better way from the mongoose docs.
var s = new Schema({ name: { type: String, unique: true }});
s.path('name').index({ unique: true });
Source: Stackoverflow.com