Mongoose: Understanding Callbacks vs Async/Await - javascript

I am trying to understand a callbacks in mongoose. I really just want to clarify my understanding so I can be sure I am on the right track.
I understanding using async/await to do queries in mongodb using mongoose. For example, the following code will get a user from my database, assuming my model "User" is set up correctly.
const getUser = async () => {
const user = await User.find({username: "John"})
return user
}
Here is my problem. I am working through the odin project and I have came across a section where callbacks are used instead of async/await. I have read some of the mongoose documentation regarding the matter without any luck.
Here is an example I am working with:
passport.use(
new LocalStrategy((username, password, done) => {
User.findOne({username: username}, (err, user) => {
if (err) {
return done(err)
}
if (!user) {
return done(null, false, {message:"Incorrect Username"})
}
if (user.password !== password) {
return done(null, false, {message: "Incorrect password"})
}
return done(null, user)
})
})
)
I understand most of what is going on here, but I do not understand how this is able to function without the use of async/await. I am looking specifically at the line:
User.findOne({username: username}, (err, user) => {callback})
My guess is that the database is queried, and if the user is found the data from the query is stored in the parameter "user."
If the query fails and no data is returned, then user becomes null and our err parameter will contain a message.
Is this correct?

You can use simple concept
async getUserList(){
try{
const users = User.findOne({username: username});
return users;
}catch(error){
throw error;
}
}
call function anywhere
console.log(getUserList());

Related

Mongoose collection.update does not update the document

I am having an issue using Mongoose's update function. Basically the document I am trying to modify is not getting updated.
Here is the code:
const user = 'Joe Bloggs'
const salt = await bcrypt.genSalt()
const pwStr = 'simplepassword'
const hashedPassword = await bcrypt.hash(pwStr, salt)
User.update({user_name: user }, { $set: { password: hashedPassword }}, function(err, doc) {
if (err) console.log('Error ', err);
console.log('Updated Doc -> ', doc); // returns un-updated doc
});
As you can see there is not much to it. The only thing I thought could be causing an issue was the bcrypt functions, but they seem to be working and hashedPassword logs out fine.
The callback logs the document, but it is not updated and when I check it in the Mongo shell it is indeed not updated.
I previously tried findOneAndUpdate but it appears that has been deprecated.
So, I tried findOne, but this also failed to update the document. Here is the basic code which uses save on the found user instead.
User.findOne({user_name: user}).then(async function(user) {
user.password = 'easypassword';
await user.save();
}
});
I tried using update in the shell using the same { $set: {...}} syntax and it works.
If anyone can tell me why this operation isn't working when I try to do it using the Mongoose functions I'd much appreciate it. Thanks in advance!
EDIT
I have tried the suggested code below:
const res = await User.updateOne([filter], [query]);
This returns the following when res is logged out:
{ acknowledged: false }
This appears in MongoDB documentation to relate to a "write concern" setting, but I have no idea where to go with it from there.
update is what is actually deprecated.
findOneAndUpdate, like other mongoose update methods, it returns the un-updated object by default. Setting the option new to true will return the updated doc.
Another option would updateOne;
User.findOneAndUpdate(
{ user_name: user },
{ $set: { password: hashedPassword } },
{ new: true },
function (err, doc) {
if (err) console.log("Error ", err);
console.log("Updated Doc -> ", doc);
}
);

Trying to use bcrypt,compare and returns Promise { <pending>}

I am trying to create a login api by using aws lambda, mongoose, and bcryptjs.
My lambda handler uses async and I am just trying to compare the user typed password with already hashed password that is in the database by using the bcrypt.compare() function in the bcryptjs module. However, my code keeps giving me Promise { } so I have tried a bunch of ways to fix this but still have an issue. I am new to using async so I might be doing totally wrong so please do not be harsh on me :)
I am getting the user account data with the encrypted password from MongoDB atlas by using the below code and it works perfectly.
let user = await User.findOne(query).select('_id name email password');
I also have a mongoose method that I have created in a user.js file just like below.
UserSchema.methods.comparePassword = function(password) {
return bcrypt.compare(password, this.password);
};
so the above method gets called and prints the result with console.log with the following code.
let passwordValid = user.comparePassword(parameters.password);
console.log('Password is validated', passwordValid);
and it gives me
INFO Password is validated Promise { <pending> }
in the lambda console.
I have done many searches so I tried using await before comparePassword like below and still not working.
let passwordValid = await user.comparePassword(parameters.password);
I have also tried resolving the returned promise by using then() like below
let passwordValid = user.comparePassword(parameters.password);
passwordValid.then(function(err, result) {
callback(null, {
"statusCode": 200,
"headers": {
"Content-Type": "application/json"
},
"body": JSON.stringify({
"success": false,
"content": result
})
});
});
However, this still does not work as I want. Only respond I receive is
{
"message": "Internal server error"
}
Have you seen this https://www.npmjs.com/package/bcrypt?
In this line, await is neede:
return bcrypt.compare(password, this.password);
As the above link suggests:
async function checkUser(username, password) {
//... fetch user from a db etc.
const match = await bcrypt.compare(password, user.passwordHash);
if(match) {
//login
}
//...
}

Object not defined when using await

I am currently trying to add some authentication to my node API.
Right now I'm using PassportJS (pretty new to this so sorry for my incompetents).
I am trying to add a local strategy and check if the users password is legit when loggin in:
// Local Strategy
passport.use(
new LocalStrategy(async (username, password, done) => {
try {
// Find user by username
const user = await User.findOne({ username })
// No user found
if (!user) {
return done(null, false)
}
console.log('user', user) // Getting output
// Check if password correct
const isMatch = await user.isValidPassword(password)
// Handle if password is not correct
if (!isMatch) {
return done(null, false)
}
// Return user
done(null, user)
} catch (err) {
done(err, false)
}
})
)
Something I've noticed is when using await on const isMatch = await user.isValidPassword(password) Postman is saying: Error: ReferenceError: user is not defined. And when I remove await it works fine, but I can type in the wrong password but I still can login. And I can see my user object when I console.log it.
{
"username": "martinnord3",
"password": "this_is_the_wrong_password"
}
Here's the isValidPassword function:
UserSchema.methods.isValidPassword = async function(newPassword) {
try {
return await bcrypt.compare(newPassword, user.password)
} catch (err) {
throw new Error(err)
}
}
I guess there's something obvious I'm missing, but I can't manage to solve this.
Thanks for taking your time to read this!
Well this is a bit awkward, but I guess it's my duty to answer my own dumb question... My function isValidPassword has this: ...user.password and I don't specify what user is in that function.. It expects this.

How to query all articles from a specific user?

CODE:
server-side
/**
* List of Articles
*/
exports.list = function (req, res) {
Article.find({ 'user.displayName': 'GIGANTOR !' }).sort('-created').populate('user', 'displayName').exec(function (err, articles) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(articles);
}
});
};
SITUATION:
What I tried above does not work. I checked the mongoose docs: http://mongoosejs.com/docs/queries.html
but can't seem to get the query to work. Currently, the query just returns nothing.
QUESTION:
How to query all articles by a user with a specific displayName ?
TL;DR You can't query a document by a field that belongs to a populated object.
Since article simply has a ref to User, you'll have just get all articles, and then filter them in memory. Or, since the article.user field is an _id, you can find articles by the user ID (but your question is asking about finding them by user.displayName).
Mongoose populate does not do the populating in the MongoDB server itself; it populates on the application server. This means that multiple round-trips to the database are happening (see article Understanding Mongoose Population.) Therefore, you can't query by a field that exists as part of a populated object.
So, here's your 2 solutions:
Article.find({}).sort('-created').populate('user', 'displayName').exec(function (err, articles) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
let filteredArticles = articles
.filter(article => article.user.displayName === 'GIGANTOR !');
res.json(filteredArticles);
}
});
Or, if you can query by _id, you can do this:
Article.find({ user: 'somemongoobjectidofuser' }).sort('-created').populate('user', 'displayName').exec(function (err, articles) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(articles);
}
});
It gets to be a bit hairy and out of scope of the question, but another solution is the aggregation pipeline, which is only usually recommended for backend analytics. But, it'll provide you more flexibility in your query (especially if you user MongoDB's new $graphLookup).
Or, you can always store a copy of the user as a denormalized object inside the article document itself, but then you run into the much-discussed issue of maintaining denormalized documents in-sync.
Just putting the code I ended up using here for people who could need it:
/**
* List of Articles
*/
exports.list = function (req, res) {
Article.find({ user: req.user._id.toString() }).sort('-created').populate('user', 'displayName').exec(function (err, articles) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(articles);
}
});
};

Nodejs variable prints on console but not on the view

the code is this
module.exports = {
index: function (req, res, next) {
//get an array of all users in user collection
Notification.find(function foundNotification(err, notifications) {
if (err) return next(err);
var elusuario=[];
User.findOne(2, function foundUser (err, user) {
if (err) return next(err);
if (!user) return next();
console.log(user);
console.log("----------------------------");
elusuario = user;
console.log(elusuario);
});
res.view({
notifications: notifications,
elusuario: elusuario
});
})
}
};
That is the controller and in the console prints elusuario good but in the view the user hasn't values. why?
i think is something is something related to the globals variables. but i dont know
thanks
EDIT
all right so the method is async. what im trying to do is find the notifications and the user by her user.id and get the user.name so what if i do this
module.exports = {
index: function (req, res, next) {
//get an array of all users in user collection
Notification.find(function foundNotification(err, notifications) {
if (err) return next(err);
User.find(function foundUser (err, users) {
if (err) return next(err);
var usuarios_locotes = [];
_.each(notifications, function (notification) {
_.each(users, function (user) {
if(notification.token_user==user.token){
console.log(user.token);
usuarios_locotes.push(user);
console.log(usuarios_locotes);
};
});
});
res.view({
notifications: notifications,
users: usuarios_locotes
});
});
})
}
};
it still not working? the __.each is an async funtion to?
sorry for all this maybe stupid questions
The method findOne of User object runs asynchronously. Because of this, you are rendering the view before the findOne returns the user object.
If you put a console.log before the render.view, it'll print the output before the console.log inner findOne method.
When the code is running, the function foundNotification is not executed before you call the res.view. My advice for you is read about Promises.
You can change your code as below to work:
function (req, res, next) {
//get an array of all users in user collection
Notification.find(function foundNotification(err, notifications) {
if (err) return next(err);
var elusuario=[];
User.findOne(2, function foundUser (err, user) {
if (err) return next(err);
if (!user) return next();
console.log(user);
console.log("----------------------------");
elusuario = user;
console.log(elusuario);
res.view({
notifications: notifications,
elusuario: elusuario
});
});
});
}
the findOne Method is an asynchrone method,so it's executed without provinding the res.view with the appropriate data
try to wrap the whole logic in the same function, it may look ugly but it ll do the thing for now
All right so.. first really thanks to everybody. I solve this shit.
I know this is not the right way to do this but it works, so for my proposes it's fine.
the problem after the EDIT was that in the view I'm trying to write an object with parameters but what I've was sending was vector of vector so changing this line:
usuarios_locotes.push(new Object(users[h]));
I can send a vector of objects.
So.. anyway thanks cause later i will change my code to do it better and efficient
This was my first post so sorry for not read the first steps of how to use this haha cause i think i have been make a lot of mistakes.
And sorry for my English :C

Categories

Resources