Mongoose - findByIdAndUpdate - doesn't work with req.body - node.js

I have a problem with update documents in mongodb over mongoose.
My model bellow:
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
var UserSchema = new mongoose.Schema({
first_name:{
type: String
},
last_name:{
type: String
},
email:{
type: String,
unique: true,
required: true
},
password:{
type: String,
required: true
},
is_active:{
type: Boolean,
default: true
},
last_login:{
type: Date
}
});
module.exports = mongoose.model('User', UserSchema);
Controller put function bellow:
exports.updateUser = function (req, res) {
console.log(req.body);
User.findByIdAndUpdate(req.body.user_id, {$set:req.body}, function(err, result){
if(err){
console.log(err);
}
console.log("RESULT: " + result);
});
res.send('Done')
}
Output on console:
Listening on port 3000... { first_name: 'Michal', last_name: 'Test' }
PUT /api/users/54724d0fccf520000073b9e3 200 58.280 ms - 4
The printed params are provided as form-data (key-value). Looks that is not working at least for me any idea what is wrong here?

You have to use req.params.user_id instead req.body.user_id
exports.updateUser = function (req, res) {
console.log(req.body);
User.findByIdAndUpdate(req.params.user_id,{$set:req.body},{new:true}, function(err, result){
if(err){
console.log(err);
}
console.log("RESULT: " + result);
res.send('Done')
});
};

I found the mistake. Note that I'm calling
req.body.user_id
where should be
req.params.user_id
url is (PUT) http://127.0.0.1:3000/api/users/54724d0fccf520000073b9e3

Further, the req.body would have key value as Text and realized as String object, inside the code. Thus, it is useful to parse the string into JSON using JSON.parse(req.body.user) - while the user is the key and { first_name: 'Michal', last_name: 'Test' } is the value.
console.log(req.body);
var update = JSON.parse(req.body.user);
var id = req.params.user_id;
User.findByIdAndUpdate(id, update, function(err, result){
if(err){
console.log(err);
}
console.log("RESULT: " + result);
res.send('Done')
});
Note: the update value is sent to Mongo DB as
{$set: { first_name : 'Michal`, last_name: 'Test' }
Further reference: Mongoose JS documentation - findByIdAndUpdate

Related

Mongoose: How to prevent mongodb to save duplicate email records in database

I want to make the key email unique across that collection but i cant getting this working, here is my server code.
// Create a schema
var userSchema = new mongoose.Schema({
email: { type: String, required: true},
password: String
});
var userModel = mongoose.model("user", userSchema);
router.post('/postuser', (req, res) => {
console.log('Requested data to server: ' + JSON.stringify(req.body._user));
var user = new userModel({
email: req.body._user.email,
password: req.body._user.password
});
// user.isNew = false;
user.save((err, data) => {
console.log('Analyzing Data...');
if(data) {
console.log('Your data has been successfully saved.');
res.json(data);
}
else {
console.log('Something went wrong while saving data.');
console.log(err);
res.send(err);
}
})
});
Note: I also try email: { type: String, required: true, unique: true} but its not working and show below error.
name: 'MongoError',
message: 'E11000 duplicate key error collection: hutreservationsystem.users
index: _Email_1 dup key: { : null }',
driver: true,
code: 11000,
index: 0,
errmsg: 'E11000 duplicate key error collection: hutreservationsystem.users index: _Email_1 dup key: { : null }',
getOperation: [Function],
toJSON: [Function],
toString: [Function] }
A short answer using this tool mongoose-unique-validator
npm install --save mongoose-unique-validator
and in your model
var mongoose = require('mongoose')
var uniqueValidator = require('mongoose-unique-validator')
var userSchema = new mongoose.Schema({
email: { type: String, required: true, unique: true},
password: String
});
userSchema.plugin(uniqueValidator)
var userModel = mongoose.model("user", userSchema);
That's it! (Notice unique: true)
Now, there is no email duplication in your collection.
Bonus! : you can access err
.catch(err => console.log(err))
so in your example
// user.isNew = false;
user.save((err, data) => {
console.log('Analyzing Data...');
if(data) {
console.log('Your data has been successfully saved.');
res.json(data);
}
else {
console.log('Something went wrong while saving data.');
console.log(err);
res.send(err);
}
accessing err >>
so you can res.send(err.message) >> 'Validation failed'
{
message: 'Validation failed',
name: 'ValidationError',
errors: {
email: {
message: 'Error, expected `email` to be unique. Value: `example#gmail.com`',
name: 'ValidatorError',
kind: 'unique',
path: 'email',
value: 'example#gmail.com'
}
}
}
Async Custom Validator
var userSchema = new mongoose.Schema({
password: String,
email: {
type: String,
lowercase: true,
required: true,
validate: {
isAsync: true,
validator: function(value, isValid) {
const self = this;
return self.constructor.findOne({ email: value })
.exec(function(err, user){
if(err){
throw err;
}
else if(user) {
if(self.id === user.id) { // if finding and saving then it's valid even for existing email
return isValid(true);
}
return isValid(false);
}
else{
return isValid(true);
}
})
},
message: 'The email address is already taken!'
},
}
});
You may like to change the validator code to es6.
email: {
type: String,
trim: true,
unique: true, // note - this is a unqiue index - not a validation
validate: {
validator: function(value) {
const self = this;
const errorMsg = 'Email already in use!';
return new Promise((resolve, reject) => {
self.constructor.findOne({ email: value })
.then(model => model._id ? reject(new Error(errorMsg)) : resolve(true)) // if _id found then email already in use
.catch(err => resolve(true)) // make sure to check for db errors here
});
},
}
},
I implemented the following code to see if anything was wrong:
var mongoose = require('mongoose');
var bodyParser = require('body-parser');
var express = require('express');
var http = require('http');
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
var Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/test');
// Create a schema
var userSchema = new mongoose.Schema({
email: { type: String, required: true, unique: true},
password: String
});
var userModel = mongoose.model("user", userSchema);
app.post('/postuser', (req, res) => {
console.log('Requested data to server: ' + JSON.stringify(req.body._user));
var user = new userModel({
email: req.body._user.email,
password: req.body._user.password
});
// user.isNew = false;
user.save((err, data) => {
console.log('Analyzing Data...');
if(data) {
console.log('Your data has been successfully saved.');
res.json(data);
}
else {
console.log('Something went wrong while saving data.');
console.log(err);
res.send(err);
}
})
});
http.createServer(app).listen(3000, function(){
console.log('Express server listening on port 3000');
});
And I made sure that no collection by the name of users existed in my local MongoDB database. Moreover, I used Postman for sending API requests to my server at http://localhost:3000. There seemed to be no issue as I continued to add users with differing email values. And I only got the following error when I entered an email with a duplicate value
{
"code": 11000,
"index": 0,
"errmsg": "E11000 duplicate key error collection: test.users index: email_1 dup key: { : \"hot#mail.com\" }",
"op": {
"email": "hot#mail.com",
"password": "1234567",
"_id": "5919a3428c13271f6f6eab0f",
"__v": 0
}
}
These are the JSON requests that I sent:
{"_user": {"email": "hot#mail.com", "password": "1234"}}
{"_user": {"email": "sammy#mail.com", "password": "1234"}}
{"_user": {"email": "tommy#mail.com", "password": "1234"}}
{"_user": {"email": "tommy#mail.ae", "password": "1234567"}}
{"_user": {"email": "hot#mail.com", "password": "1234567"}}
The error mentioned above was sent back on the last request as the email hot#mail.com is repeated.
If you view the link http://mongoosejs.com/docs/api.html#schematype_SchemaType-unique , you'll see that the E11000 error is only sent when the email entered is not unique. Moreover, your email can't be an empty string or not be present as that violates the required property.
ES6:
const userSchema = new Schema({
name: String,
id: {
type: String,
required: true,
unique: true,
validate: async (value) => {
try {
const result = await userModel.findOne({ id: value })
if (result) throw new Error("duplicity detected: id :" + value);
} catch (error) {
throw new Error(error);
}
}
}
})
const userModel = mongoose.model<Document>('users', userSchema);
ES6 (TypeScript):
const userSchema = new Schema({
name: String,
id: {
type: String,
required: true,
unique: true,
validate: async (value: any): Promise<any> => {
try {
const result: Document | null = await userModel.findOne({ id: value })
if (result) throw new Error("duplicity detected: id :" + value);
} catch (error) {
throw new Error(error);
}
}
}
})
const userModel: Model<Document, {}> = mongoose.model<Document>('users', userSchema);
In your user schema set attribute email as unique (unique: true).
var userSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true}, });

Removing a child element in subdocument not working

I'm fairly new to Mongoose and don't think my approach on deleting an item in a subdocument is the right one.
I have the following schema setup:
//DEPENDENCIES
var mongoose = require('mongoose');
var contactSchema = new mongoose.Schema({
name:{type:String},
age:{type:Number}
});
var phoneSchema = new mongoose.Schema({
number:{ type: String },
phoneType:{ type: Number }
})
var memberSchema = new mongoose.Schema({
firstname: {
type: String
},
lastname: {
type: String
},
phone:[phoneSchema],
contacts:[contactSchema]
});
//RETURN MODEL
module.exports = mongoose.model('member', memberSchema);
To remove an item from the phone, in my Express API, I first find the parent then reference "remove" for the child ID, like this. But it does not work.
router.route('/owner/:ownerId/phone/:phoneId')
.delete(function(req, res){
Member.findOne({_id: req.body.ownerId}, function(err, member){
member.phone.remove({_id: req.body.phoneId}, function(err){
if(err)
res.send(err)
res.json({message: 'Success! Phone has been removed.'})
});
});
});
Figured out that I was looking for req.body and was actually needing req.params.
Also found right syntax on Mongoose docs:
router.route('/owner/:ownerId/phone/:phoneId')
.delete(function(req, res){
Member.findOne({_id: req.params.ownerId}, function(err, member){
member.phone.id(req.params.phoneId).remove();
member.save(function (err) {
if (err) return handleError(err);
console.log('the sub-doc was removed');
});
});
});

Mongo db, how to give object _id another collection's document

I have 2 collections called User and Location. In User, there is a location _id and this is an Object. Id also references the location collection. My question is what did I do wrong? When I call getUser function I want to see user information and the user's location information. What I need to do ?
User Schema
module.exports = (function userSchema() {
var Mongoose = require('mongoose');
var userSchema = Mongoose.Schema({
name: {
type: String,
require: true
},
surname: {
type: String,
require: true
},
tel: {
type: String,
require: true
},
age: {
type: String,
require: true
},
mevki_id: {
type: String,
require: true
},
location_id: [{
type: Mongoose.Schema.Types.ObjectId,
ref: 'locations'
}]
});
var collectionName = 'users';
var User = Mongoose.model(collectionName, userSchema);
return User;
})();
User Controller
function userController() {
var User = require('../models/UserSchema');
this.createUser = function (req, res, next) {
var name = req.body.name;
var surname = req.body.surname;
var tel = req.body.tel;
var age = req.body.age;
var mevki_id = req.body.mevki_id;
var lok_id = req.body.lok_id;
User.create({
name: name,
surname: surname,
tel: tel,
age: age,
mevki_id: mevki_id,
lok_id: lok_id
}, function (err, result) {
if (err) {
console.log(err);
return res.send({
'error': err
});
} else {
return res.send({
'result': result,
'status': 'successfully saved'
});
}
});
};
this.getUser = function (req, res, next) {
User.find()
.populate('lok_id')
.exec(function (err, result) {
if (err) {
console.log(err);
return res.send({
'error': err
});
} else {
return res.send({
'USERS': result
});
}
});
};
return this;
};
module.exports = new UserController();
First, your schema is wrong:
var userSchema = new Mongoose.Schema({
// ...
location_id: { type: [Mongoose.Schema.Types.ObjectId], ref: 'locations' }
})
Second, in your schema the last field name is location_id while in your controller, you change it to lok_id.
So, fix this:
User.create({
// ...
location_id: lok_id
}
and this:
User
.find()
.populate('location_id')
UPDATE
In your json the last field name is location_id, therefore, fix this too:
this.createUser = function (req, res, next) {
// ...
var lok_id = req.body.location_id;
}

Mongoose virtual field not updated

i created a schema for user like this:
var schema = new Schema({
username: {
type: String,
unique: true,
required: true
},
hashedPassword: {
type: String,
required: true
},
salt: {
type: String,
required: true
}
});
schema.virtual('password')
.set(function(password) {
this._plainPassword = password;
this.salt = Math.random() + '';
this.hashedPassword = this.encryptPassword(password);
})
.get(function() { return this._plainPassword; });
schema.methods.encryptPassword = function(password) {
return crypto.createHmac('sha1', this.salt).update(password).digest('hex');
};
then I'm trying to change the password using the two approaches:
work nice
User.findById('userId..', function(err, user) {
user.password = '456';
user.save(cb);
})
why is this approach not working ?
User.findByIdAndUpdate('userId', {$set: {password: '456'}}, cb)
This happens because Mongoose doesn't apply any of the following on a findByIdAndUpdate() operation:
defaults
setters
validators
middleware
From the docs:
If you need those features, use the traditional approach of first
retrieving the document.
Model.findById(id, function (err, doc) {
if (err) ..
doc.name = 'jason borne';
doc.save(callback);
})
with version 4.0.9+ middleware supports findByIdAndUpdate().
CustomerSchema.pre('findOneAndUpdate', function(next, done) {
console.log("findOneAndUpdate pre middleware!!!");
next();
});

mongoose index not work, when use mocha

My project files:
I have a User model:
user.model.js
var UserSchema = new Schema({
username: { type: String, required: true, unique: true },
location: { 'type': {type: String, enum: "Point", default: "Point"}, coordinates: { type: [Number], default: [0,0]} },
});
UserSchema.index({location: '2dsphere'});
And have an Express route:
user.route.js
function create(req, res) {
User.create(req.body, function(err, user) {
if (err) return res.send(err);
return res.send(user);
})
}
user.test.js
describe('test_user', function() {
it('create user', function(done) {
request('http://localhost/user')
.post('/')
.send({
username: 'john',
password: "123"
})
.end(function(err, res) {
if (err) cb(err);
done()
});
});
});
I have a drop_databse.js file, it run every time before run test:
drop_databse.js
var MongoClient = require('mongoose/node_modules/mongodb').MongoClient
MongoClient.connect('mongodb://localhost/miccity_test', function(err, db) {
if (err) return console.log(err);
db.dropDatabase(function(err) {
if (err) return console.log('drop databse failed!! :\n' + err);
db.close();
})
})
when I test
when I run test:
$ node drop_database.js && mocha
test_user
✓ create user
1 passing (21ms)
But, I notice user collection in database has not index of location field !
But I create a single file:
test.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/miccity_test');
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'mongodb connection error:'));
var UserSchema = new Schema({
username: { type: String, required: true, unique: true },
location: { 'type': {type: String, enum: "Point", default: "Point"}, coordinates: { type: [Number], default: [0,0]} },
});
UserSchema.index({location: '2dsphere'});
var User = mongoose.model('User', UserSchema);
User.create({username: "test"}, function (err) {
if (err) console.log(err);
})
And run: node drop_databse.js && node test.js, then i check the index of location, it exist!
Is it mean mocha can not create index of collection?
Edit:
i found the trick way:
use Model.ensureIndexes to Sends ensureIndex commands to mongo for each index declared in the schema.
function create(req, res) {
User.ensureIndexes(function(err){
User.create(req.body, function(err, user) {
if (err) return res.send(err);
return res.send(user);
})
}
it is maybe a bug for mongoose, I already submit this issue to github.

Resources