mongodb query with javascript, cursor methods - node.js

I am just creating a simple user login for using MongoDB, Backbone and Node with Express.
I am stuck on querying the database for the user credentials and reliably identifying whether they exist or not.
I'm trying two things here:
// When user posts to the url
app.post('/save/user', function (req, res) {
// create user object and store email from form
var user = {
email : req.body.email
};
// in my collection users
db.collection('users', function (err, collection) {
// create a variable existence with the value, in lamens terms 'yes it does' or 'no it doesnt'
var existence = findOne(collection, {
$query : {email : user.email}
});
});
};
// findOne function passing the collection and the query object
function findOne(coll, query) {
var cursor = coll.find(query).limit(1);
return cursor.hasNext() ? cursor.next() : null;
}
So this is the first way I've been trying. Thing is, I don't understand why the cursor doesn't have 'next(), hasNext(), findOne(), forEach()' and other methods for javascript environment but only for mongo shell.
My question is, how do I access these methods from in my Node.js app?
The second way I tried it :
// When user posts to the url
app.post('/save/user', function (req, res) {
// create user object and store email from form
var user = {
email : req.body.email
};
// in my collection users
db.collection('users', function (err, collection) {
// Have a look for the email entered
var query = collection.find({
$query : {email : user.email}
}).limit(1);
// If it's true, then send response
query.each( function (err, item) {
// If the item does exists, then send back message and don't insert. Otherwise, insert.
if(item !== null) {
console.log('Item does not equal null : ', item);
res.send('The email : ' + item.email + ' already exists');
} else {
console.log('Item does equal null : ', item);
collection.insert(user);
res.send('New user with email : ' + user.email + ' was saved');
}
});
});
};
The problem with this is, it's always going to return null at some point and so I am going to warn the user 'it already exists' and then the next time will be null so it's going to save the email.
I think i'm missing the point so a point in the right direction would be great.
Many thanks in advance!
Well i've looked into a solution but still must be missing the point.
I'm doing an insert, passing in my user object with safe : true but although multiple user objects can be entered, it's still only looking for identical ID;s. I've tried creating an id with new ObjectID() but I still don't understand if a user enters their email adress, thenattempts to create a new user with the same email, it would create a new id for that entry.
By doing the findOne, I can see if it exists easily, Idon't see how it can be done with insert.
app.post('/register/user', function (req, res) {
// Store user details in object
var user = {
username : req.body.user,
email : req.body.email,
password : req.body.password
};
db.collection('users', function (err, collection) {
collection.insert(user, {safe : true}, function (err, doc) {
console.log('what is doc : ', doc);
if(!err) {
res.writeHead(200, {
"Content-Type" : "text/plain",
"Message" : "New user added",
"Access-Control-Allow-Origin" : "*"
});
} else {
console.log('Error is : ', err);
res.writeHead(200, {
"Content-Type" : "text/plain",
"Message" : "User already exists",
"Access-Control-Allow-Origin" : "*"
});
}
res.end();
});
});
});

If you're using the Native Node.JS MongoDB driver, there is a collection.findOne() method and cursors do have nextObject() and each() methods equivalent to the shell methods hasNext() and forEach(). The implementation/naming of the mongo shell equivalents can vary slightly between drivers but you should be able to translate mongo shell examples into Node.JS.
As far as saving a new user goes .. instead of querying for the user before inserting, you would be better to add a unique index on the email field and insert into the collection with safe:true. You can then handle the err result if the insert fails due to a duplicate email. If you do a query before insert there is a potential race condition where the email might not exist when you check for it, but does exist by the time you do the insert.

Related

I want to check another field When I get dublicate key error for email (unique) in mongodb findoneandupdate

User.findOneAndUpdate({ _id: userFindByid }, {
$set: {
"email": req.body.email,
"username": req.body.username,
"phone_number": req.body.phone_number,
"address": req.body.address,
"isBenefactor": req.body.isBenefactor,
"location": req.body.location
}
}, { runValidators: true, context: 'query' }, (err, doc) => {
if (err) {
// if request email has already exist in db I want to check that emails isDeleted field in here . if isDeleted is true I want to update .
return res.status(500).json({ message: err.message });
}
else {
return res.status(200).json({ message: 'Your account was updated' });
}
})
//
Let me explain scenario clearly,
I registered with an email address(first#gmail.com) then I deleted my account =>(first#gmail.com)=>isDeleted=true
After that I again registered with another email address(second#gmail.com)=>isDeleted=false
Now I want to update my second email address with first one I will get an unique key error because (first#gmail.com) is in mydb ,but I have to da update process because (first#gmail.com)=>IsDelete=true
If I use { 'email': req.body.email, 'isDeleted': true} I can not update (second#gmail.com)=>isDeleted=false
I can fix the problem by using too much if statements , but I dont want to use if statements too much. I am looking for best practice for that problem.
I hope I could explain
Here is my code block , can someone help me ?
THIS ANSWER ASSUMES YOU ARE USING MONGOOSE!
One way you can do is instead of using findOneAndUpdate you can use .save this way you can issue a hook on mongoose.
For example, you would do User.save(...) then you go to your schema code and you add the following (assuming your schema name is UserSchema)
UserSchema.post('save', function(error, doc, next) {
// Error code 11000 means this is a duplicate
if (error.name === 'MongoError' && error.code === 11000) {
// So instead of throwing an error you would do anything you want
// Such as look for the other record and delete it, update its isDelete
// field, remove email, etc... really is up to you
}
next()
});
EDIT: Of course before User.save(...) you need to find the user!
For example,
User.findOne({_id:1}, function(err, doc){
// Update doc values
// Finally do doc.save(...)
})
You can make it look much better by using the async library and using async.waterfall
EDIT2: Okay so now that I understand your requirement better, here is your best solution in my opinion.
Find the user you want to update
Change email
Save
On the other side, you need to have a hook (unfortunately its poorly documented, you have to do your own digging but here is a link to mongoose documentation)
Here is how this will work
1. Hook a pre save (before actually saving execute specific block of code)
2. The block of code will only execute when the email is modified (we dont want to execute it everytime, its just a waste of resources)
3. The block of code will use deleteOne and delete the user matching that email
NOTE: For best performance make sure to index the email and make it unique!
I have created the full (similar to what you want) project with the code on here
But if you wish here are also some snippet
// This will run before saving the object
UserSchema.pre('save', function (next) {
let user = this
// Make sure to run only when email is modified
if(user.isModified('email')) {
// IF the email was modified, then attempt to delete a record with this email (if there is one, then it will be deleted otherwise it will just continue)
this.constructor.deleteOne({email:this.email, isDeleted:true}, (err) => {
err ? next(err) : next()
})
} else {
next()
}
})
// Code to save/update the user
User.findOne({_id:"1"}, function(err, user) {
if (err) {
throw err
} else {
if (user) {
user.email = "test2#test.com"
user.save(err => {
err ? console.log(err) : console.log("Success!")
})
} else {
console.log("User was not found!")
}
}
})
Good luck!

Issue with mongoose .findByIdAndUpdate and pre-update hook

I have mongoose-schema called UserSchema, which stores information about all users.
I want to enable the user to change his information, which I try by using .findByIdAndUpdate.
This is the relevant code:
router.post("/updateprofile", function(req,res,next) {
const {id, org, tel, email, firstName, lastName} = req.body;
Users.findByIdAndUpdate(id, {org : org, tel : tel, email : email, firstName : firstName , lastName : lastName}, function (err, response) {
if (err) throw err
res.json(response);
});
});
However, when trying to change the info, I get the following error-message: Cannot read property 'password' of undefined. I'm pretty sure this is caused by a pre-update hook, but I can not remove it because I need it for my "forgot-password"-functionality.
Here's the code:
UserSchema.pre('findOneAndUpdate', function (next) {
this.update({},{ $set: { password:
bcrypt.hashSync(this.getUpdate().$set.password, 10)}} )
next();
});
I'm confused by why it used that prehook anyway, since in the hook it's looking for findOneandUpdate and when I try to change the data I'm using findByIdAndUpdate.
I tried using .update() instead but that doesn't work either. Does anyone know what I'm doing wrong and how to fix it?
Looks like getUpdate isn't what you want, try it like this:
UserSchema.pre('findOneAndUpdate', function (next) {
this._update.password = bcrypt.hashSync(this._update.password, 10)
next();
});
With regards to your second question, findByIdAndUpdate is a wrapper around findOneAndUpdate. Here's the code straight from Mongoose's source code for your reference
Model.findByIdAndUpdate = function(id, update, options, callback) {
if (callback) {
callback = this.$wrapCallback(callback);
}
if (arguments.length === 1) {
if (typeof id === 'function') {
var msg = 'Model.findByIdAndUpdate(): First argument must not be a function.\n\n'
+ ' ' + this.modelName + '.findByIdAndUpdate(id, callback)\n'
+ ' ' + this.modelName + '.findByIdAndUpdate(id)\n'
+ ' ' + this.modelName + '.findByIdAndUpdate()\n';
throw new TypeError(msg);
}
return this.findOneAndUpdate({_id: id}, undefined);
}
The comments in the code read:
/**
* Issues a mongodb findAndModify update command by a document's _id field.
* `findByIdAndUpdate(id, ...)` is equivalent to `findOneAndUpdate({ _id: id }, ...)`.
*
You can read the source code for yourself here: https://github.com/Automattic/mongoose/blob/9ec32419fb38b74b240280aaba162f9ee4416674/lib/model.js
Assuming you plan to pass the entire document in as an update (such as with upsert: true - I think this is also assumed in the accepted answer) and want to use one function, this works:
async function validation(next, self) {
// validation code here
}
YourSchema.pre('validate', async function(next) { validation(next, this) });
YourSchema.pre('findOneAndUpdate', async function(next) { validation(next, this._update) });
You just replace this with this._update to inform the function what it's validating.
Since I wasn't able to access _update property, this worked to me:
UserSchema.pre('findOneAndUpdate', async function (this) {
let update = {...this.getUpdate()};
// Only run this function if password was modified
if (update.password){
// Hash the password
const salt = genSaltSync();
update.password = await hash(this.getUpdate().password, salt);
this.setUpdate(update);
}
})

Object #<Promise> has no method 'limit'

when I run code
var collection = db.get('categories');
console.log(collection.find().limit(1).sort( { _id : -1 } ));
on nodejs using mongodb I am getting error Object # has no method 'limit' . I am a beginner to node and really stuck on this section of node
here is full code for geting last insert document.
router.post('/addcategory', function(req, res) {
// Set our internal DB variable
var db = req.db;
// Get our form values. These rely on the "name" attributes
var name = req.body.name;
var description = req.body.description;
// Set our collection
var collection = db.get('categories');
// Submit to the DB
collection.insert({
"name" : name,
"description" : description,
}, function (err, doc) {
if (err) {
// If it failed, return error
res.send("There was a problem adding the information to the database.");
}
else {
// And forward to success page
/******************/
console.log(collection.find().limit(1).sort( { _id : -1 } ));
/*************/
}
});
});
The key piece of missing information here was that you are using Monk, not the native MongoDB Node.JS driver. The command you have for find() is how you would use the native driver (with the changes suggested by #BlakesSeven above for asynchronity), but Monk works a little bit differently.
Try this instead:
collection.find({}, { limit : 1, sort : { _id : -1 } }, function (err,res) {
console.log(res);
});
The method is still asynchronous so you still need to invoke itm either as a promise with .then() or a callback. No methods are sychronous and return results in-line.
Also the result returned from the driver is s "Cursor" and not the object(s) you expect. You either iterate the returned cursor or just use .toArray() or similar to convert:
collection.find().limit(1).sort({ "_id": -1 }).toArray().then(function(docs) {
console.log(docs[0]);
});
Or:
collection.find().limit(1).sort({ "_id": -1 }).toArray(function(err,docs) {
console.log(docs[0]);
});
But really the whole premise is not correct. You seem to basically want to return what you just inserted. Event with the correction in your code, the returned document is not necessarily the one you just inserted, but rather the last one inserted into the collection, which could have occurred from another operation or call to this route from another source.
If you want what you inserted back then rather call the .insertOne() method and inspect the result:
collection.insertOne({ "name": name, "description": description },function(err,result) {
if (err) {
res.send("There was an error");
} else {
console.log(result.ops)
}
});
The .insert() method is considered deprecated, but basically returns the same thing. The consideration is that they return a insertWriteOpResult object where the ops property contains the document(s) inserted and their _id value(s),

MongoDB node native driver creating duplicate documents

I'm getting a duplicate document when using the mongodb-native-driver to save an update to a document. My first call to save() correctly creates the document and adds a _id with an ObjectID value. A second call creates a new document with a text _id of the original ObjectID. For example I end up with:
> db.people.find()
{ "firstname" : "Fred", "lastname" : "Flintstone", "_id" : ObjectId("52e55737ae49620000fd894e") }
{ "firstname" : "Fred", "lastname" : "Flintstone with a change", "_id" : "52e55737ae49620000fd894e" }
My first call correctly created Fred Flinstone. A second call that added " with a change" to the lastname, created a second document.
I'm using MongoDB 2.4.8 and mongo-native-driver 1.3.23.
Here is my NodeJS/Express endpoint:
app.post("/contacts", function (req, res) {
console.log("POST /contacts, req.body: " + JSON.stringify(req.body));
db.collection("people").save(req.body, function (err, inserted) {
if (err) {
throw err;
} else {
console.dir("Successfully inserted/updated: " + JSON.stringify(inserted));
res.send(inserted);
}
});
});
Here is the runtime log messages:
POST /contacts, req.body: {"firstname":"Fred","lastname":"Flintstone"}
'Successfully inserted/updated: {"firstname":"Fred","lastname":"Flintstone","_id":"52e55737ae49620000fd894e"}'
POST /contacts, req.body: {"firstname":"Fred","lastname":"Flintstone with a change","_id":"52e55737ae49620000fd894e"}
'Successfully inserted/updated: 1'
Why doesn't my second update the existing record? Does the driver not cast the _id value to an ObjectID?
What you are posting back the 2nd time contains a field named "_id", and it's a string. That is the problem.
Look at the document, what the save method does is a "Simple full document replacement function". I don't use this function quit often so here's what I guess. The function use the _id field to find the document and then replace the full document with what you provided. However, what you provided is a string _id. Apparently it doesn't equal to the ObjectId. I think you should wrap it to an ObjectId before passing to the function.
Besides, the save method is not recommended according to the document. you should use update (maybe with upsert option) instead
I don't exactly know why a second document is created, but why don't you use the update function (maybe with the upsert operator)?
An example for the update operation:
var query = { '_id': '52e55737ae49620000fd894e' };
db.collection('people').findOne(query, function (err, doc) {
if (err) throw err;
if (!doc) {
return db.close();
}
doc['lastname'] = 'Flintstone with a change';
db.collection('people').update(query, doc, function (err, updated) {
if (err) throw err;
console.dir('Successfully updated ' + updated + ' document!');
return db.close();
});
});
And now with the upsert operator:
var query = { '_id': '52e55737ae49620000fd894e' };
var operator = { '$set': { 'lastname': 'Flintstone with a change' } };
var options = { 'upsert': true };
db.collection('people').update(query, operator, options, function (err, upserted) {
if (err) throw err;
console.dir('Successfully upserted ' + upserted + ' document!');
return db.close();
});
The difference is that the upsert operator will update the document if it exist, otherwise it will create a new one. When using the upsert operator you should keep in mind that this operation can be underspecified. That means if your query does not contain enough information to identify a single document, a new document will be inserted.

check an item collection value mongodb

I would like to know what can I do to check an intem of a collection in mongodb.
the function that I describe below insert users in the db and I would check before the insert if the username exists.
exports.adduser = function(db) {
return function(req, res) {
var userName = req.body.username;
var userEmail = req.body.useremail;
var userCod = req.body.usercod;
var collection = db.get('usercollection');
collection.insert({
'username' : userName,
'email' : userEmail,
'cod' : userCod
}, function (err, doc) {
if (err) {
res.send('There was a problem adding the information to the database.');
}
else {
res.location('userlist');
res.redirect('userlist');
}
});
}
}
var collection = db.get('usercollection');
collection.count({username:username}, function(err, count){
if(err){
return callback(err);
}
if(count > 0){
return callback('User already exists!');
}
//do your insert operation
});
I don't know diddly squat about node.js, but lookups in MongoDB are on the Collection object as find() and findOne(). Assuming your username is enough to make a record unique, the "check if exists" would look like
if ( collection.findOne( { "username" : userName } ) != null ) {
println("username " + userName + " exists!" );
}
The obvious approach would be to add a unique index on username, which will ensure you can't insert duplicates.
The error handling logic will be similar to what you've sketched out in the question description. If a username already existed, there would be an exception when trying to insert and the err string would be something like "E11000 duplicate key error... " (including the offending duplicate key).

Resources