Instance methods not defined for Mongoose findOne() query result? - node.js

My problem is that I can't call a model instance method on the document returned from a findOne() query, even though I can successfully call the instance method on the same document before I save it to the collection.
First, the usual require()s, followed by schema creation:
var express = require('express');
var router = express.Router();
const mongoose = require('mongoose');
const pwHash = require('password-hash');
const dbAddress = 'mongodb://localhost/18-652_Ramp_Up_Project';
const projectDb = mongoose.createConnection(dbAddress);
const exampleUserSchema = new mongoose.Schema({
username: String,
password: String,
});
// Instance methods of the User class:
exampleUserSchema.methods.checkPw = function(pwToAuthenticate) {
return pwHash.verify(pwToAuthenticate, this.password);
};
// Retrieve the document corresponding to the specified username.
exampleUserSchema.statics.findOneByUsername = function(username) {
return new Promise((resolve, reject) => {
projectDb.collection('exampleUsers').findOne(
{username: username},
(err, existingUser) => {
if (err) throw(err);
return resolve(existingUser ? existingUser : null);
}
);
});
};
// Constructor (through mongo);
const ExampleUser = projectDb.model('ExampleUser', exampleUserSchema, 'exampleUsers');
Now the interesting part: I can create a new user and call the instance method on it successfully:
// Create a test user and try out the
const hashedPw = pwHash.generate('fakePassword');
ExampleUser.create({username: 'fakeUsername', password: hashedPw}, (err, newUser) => {
console.log(newUser.checkPw('fakePassword')); // --> true
console.log(newUser.checkPw('password123')); // --> false
newUser.save((err) => {
console.log('New user ' + newUser.username + ' saved.'); // --> New user fakeUsername saved.
});
});
But when I try to call it on the document returned from a findOne() query, it's "not a function":
/* POST user login info. */
router.post('/login', (req, res, next) => {
// Try to look up the user in the database.
ExampleUser.findOneByUsername(req.body.username) // Static method works.
.then((existingUser) => {
console.log(existingUser);
if (existingUser && existingUser.checkPw(req.body.password)) { // Instance method doesn't.
console.log('Access granted.');
} else {
console.log('Access denied.');
}
});
});
module.exports = router;
This produces:
POST /example/login 200 22.587 ms - 14
{ _id: 5b3d592d8cd2ab9e27915380,
username: 'fakeUsername',
password: 'sha1$ea496f95$1$6a01df977cf204357444e263861041c622d816b6',
__v: 0 }
(node:40491) UnhandledPromiseRejectionWarning: TypeError: existingUser.checkPw is not a function
at ExampleUser.findOneByUsername.then (/Users/cameronhudson/Documents/GitHub/18-652_Ramp_Up_Project/routes/exampleUsers.js:52:42)
at process._tickCallback (internal/process/next_tick.js:68:7)

Related

Promise { <pending> } React bcrypt hashing the Password [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 9 months ago.
I am creating a Login/Registration Form Using Nodejs. I am hashing the password entered by the user using bcrypt.js but when I assign the password to a variable so that I push that to the database I get this error "Promise { pending }".
I am learning nodejs and react so I do not know too much about this can someone help me.
Thanks!
The Code That I am running is:
################################
const express = require('express');
const app = express();
const mysql = require('mysql2');
const bcrypt = require('bcryptjs');
const cors = require('cors');
// Need this to make api request from backend
app.use(cors());
/**using this express will format the data automatically in json format */
app.use(express.json()); /**Use This Otherwise you get the req.body undefined */
const port = 3001;
const securePassword = async (password) => {
const passwordHash = await bcrypt.hash(password, 4);
return passwordHash;
};
const db = mysql.createConnection({
user: 'root',
host: 'localhost',
password: 'newpassword',
database: 'INSTAGRAM',
});
// Getting Data From Signup Form of React
app.post('/signup', (req, res) => {
const emailaddress = req.body.emailaddress;
const fullname = req.body.fullname;
const username = req.body.username;
const password = req.body.password;
const hashPass = securePassword(password);
console.log(hashPass);
// Checking If Use Already Exist
db.query(
'SELECT * FROM USER WHERE username = ? OR email = ? ',
[username, emailaddress],
(err, result) => {
if (err) {
res.send({ err: err });
} else {
if (result.length > 0) {
res.send({ message: 'Username/Email Already Exist' });
} else {
db.query(
'INSERT INTO USER (username, fullname, email, password) VALUES (?, ?, ?, ?)',
[username, fullname, emailaddress, hashPass],
(err, result) => {
if (err) {
res.send(err);
} else {
res.send(result);
}
}
);
}
}
}
);
});
// Starting the server on port 3001
app.listen(port, () => {
console.log(`SERVER STARTED ${port}`);
});
First of all for better and more professional coding try to break your code into multiple functions in multiple .js files .
then you should pass a function to validate the inputs otherwise any data can be passed to db without being validated .
and then you can use this codes for user Registration :
app.js file :
const express = require('express');
const app = express();
const userRouter = require('./routes/user.routes');
app.use(express.json());
app.use('/user', userRouter);
user.routes file :
const express = require('express');
const userRouter = express.Router();
const {httpHandleSignUp} = require('../controllers/user/user.controller');
userRouter.post('/signup', httpHandleSignUp);
module.exports = userRouter
and then for handling Registration you can create a controller file and first of all check the inputs :
httpHandleSignUp controller code :
async function handleSignUp(req, res) {
const values = req.body
const errors = await validateInputs(values, res);
if(errors.length == 0) {
await addUserToDB(values, res);
} else {
res.json(errors)
}
}
you can use any validation you want like code below :
async function validateInputs(values, res) {
let errors = [];
if(!values.name || !values.email || !values.password) {
errors.push('missing required inputs');
}
if(!/\S+#\S+\.\S+/.test(values.email)) { // regex : string#string.string
errors.push('invalid email address ');
}
if(values.password.length < 8) {
errors.push('entered password is too short !');
}
if(await checkDbForEmail(values.email)) {
errors.push('a user with this email already registered !');
}
// TODO : add more validation
return errors;
}
and also you need to a function to check db for already registered users which used in above function :
async function checkDbForEmail(email) {
return await user.findOne({
email: email
});
}
now if there is NO errors the user will be added to DB by this function :
async function addUserToDB(values, res) {
bcrypt.hash(values.password, saltRounds)
.then(hashedPass => {
user.create({
name: values.name,
email: values.email,
password: hashedPass
}, (err, user) => {
res.json({
ok : 'user added to db successfully',
data: {
name: user.name,
email: user.email
}
});
});
})
.catch( (err) => console.log(err));
}
tip: this code works with mongo you may need to changes DB functions.

Mongoose middleware schema.pre('save', ...)

I am making a REST API in NodeJS using the Mongoose Driver. I want to hash the passwords before saving them. For the same, I am using Mongoose Schema, where I made a userSchema for my user model. And for hashing I used the following function.
userSchema.pre('save', async (next) => {
const user = this;
console.log(user);
console.log(user.isModified);
console.log(user.isModified());
console.log(user.isModified('password'));
if (!user.isModified('password')) return next();
console.log('just before saving...');
user.password = await bcrypt.hash(user.password, 8);
console.log('just before saving...');
next();
});
But on creating a user or modifying it I am getting Error 500, and {} is getting returned. My routers are as follows.
router.post('/users', async (req, res) => {
const user = User(req.body);
try {
await user.save();
res.status(201).send(user);
} catch (e) {
res.status(400).send(e);
}
});
router.patch('/users/:id', async (req, res) => {
const updateProperties = Object.keys(req.body);
const allowedUpdateProperties = [
'name', 'age', 'email', 'password'
];
const isValid = updateProperties.every((property) => allowedUpdateProperties.includes(property));
if (!isValid) {
return res.status(400).send({error: "Invalid properties to update."})
}
const _id = req.params.id;
try {
const user = await User.findById(req.params.id);
updateProperties.forEach((property) => user[property] = req.body[property]);
await user.save();
if (!user) {
return res.status(404).send();
}
res.status(200).send(user);
} catch (e) {
res.status(400).send(e);
}
});
And the following is my console output.
Server running on port 3000
{}
undefined
On commenting out the userSchema.pre('save', ...) everything is working as expected. Please can you help me figure out where am I going wrong.
Using function definition instead of arrow function for mongoose pre save middleware:
userSchema.pre('save', async function(next) { // this line
const user = this;
console.log(user);
console.log(user.isModified);
console.log(user.isModified());
console.log(user.isModified('password'));
if (!user.isModified('password')) return next();
console.log('just before saving...');
user.password = await bcrypt.hash(user.password, 8);
console.log('just before saving...');
next();
});
Update:
The difference is this context, if you use arrow function in this line const user = this;, this now is your current file (schema file, I guess).
When you use function keyword, this context will belong to the caller object (user instance).

TypeError: newUser.find is not a function

I am very new to the MERN stack and I would like some help figuring out this error. I'm trying to check if an email is already in the database upon creating a new user. Can anyone tell me why I am getting this error?
The model and scheme
//schema
const Schema = mongoose.Schema;
const VerificationSchema = new Schema({
FullName: String,
email: String,
password: String,
date: Date,
isVerified: Boolean,
});
// Model
const User = mongoose.model("Users", VerificationSchema);
module.exports = User;
The Api
const express = require("express");
const router = express.Router();
const User = require("../Models/User");
router.get("/VerifyEmail", (req, res) => {
console.log("Body:", req.body);
const data = req.body;
const newUser = new User();
newUser.find({ email: data.email }, function (err, newUser) {
if (err) console.log(err);
if (newUser) {
console.log("ErrorMessage: This email already exists");
} else {
console.log("This email is valid");
}
});
res.json({
msg: "We received your data!!!",
});
});
module.exports = router;
The api caller using axios
const isEmailValid = (value) => {
const info = {
email: value,
};
axios({
url: "http://localhost:3001/api/VerifyEmail",
method: "get",
data: info,
})
.then(() => {
console.log("Data has been sent");
console.log(info);
})
.catch(() => {
console.log("Internal server error");
});
};
if you have body in your request, change the type of request to POST...
after that for use find don't need to create a instance of model, use find with Model
router.get("/VerifyEmail", (req, res) => {
console.log("Body:", req.body);
const data = req.body;
User.find({ email: data.email }, function (err, newUser) {
if (err) console.log(err);
if (newUser) {
console.log("ErrorMessage: This email already exists");
} else {
console.log("This email is valid");
}
});
res.json({
msg: "We received your data!!!",
});
});
I prefer to use async/await and don't use Uppercase world for routing check the article: like this
router.post("/verify-email", async (req, res) => {
try {
let { email } = req.body;
let newUser = await User.findOne({ email });
if (newUser) {
console.log("ErrorMessage: This email already exists");
} else {
console.log("This email is valid");
}
} catch (error) {
res.json({
msg: "somthing went wrong",
});
}
res.json({
msg: "We received your data!!!",
});
});
The proper way to query a Model is like so:
const User = mongoose.model('Users');
User.find({<query>}, function (err, newUser) {...
So you need to get the model into a variable (in this case User) and then run the find function directly against it, as opposed to running it against an object you instantiate from it. So this is incorrect:
const newUser = new User();
newUser.find(...
So assuming all your files and modules are linked up correctly, this should work:
const User = require("../Models/User");
User.find({<query>}, function (err, newUser) {...
The problem wasn't actually the mongoose function but I needed to parse the object being sent.
let { email } = JSON.parse(req.body);
Before parsing the object looked like {"email" : "something#gmail.com"}
and after parsing the object looked like {email: 'something#gmail.com'}
I also changed the request from 'get' to 'post' and instead of creating a new instance of the model I simply used User.find() instead of newUser.find()

How can I implement transaction concept in mongoose model?

I have three models "userLogin.js","userDetail.js",and "userAddress.js".I want data should be stored simultaneously, if any error occurs it should rolback all the insert actions.this what I have tried. I gives me the error user is not defined . when try to fix them it gives the error "schema is not registered"
const UserLogin=require("../models/userLogin");
const UserDeatil=require("../models/userDetail");
var myModelSchema1 = require('mongoose').model('UserLogin').schema;
var myModelSchema2 = require('mongoose').model('UserDeatils').schema;
exports.user_signup = (req, res, next) => {
UserLogin.find({ email: req.body.email })
.exec()
.then(user => {
if (user.length >= 1) {
return res.status(409).json({
message: "Mail exists"
});
} else {
bcrypt.hash(req.body.password, 10, (err, hash) => {
if (err) {
return res.status(500).json({
error: err
});
} else {
const user = new UserLogin({
_id: new mongoose.Types.ObjectId(),
email: req.body.email,
password: hash,
loginDate:req.body.logindate,
});
const userdetils = new UserDeatil({
_id: new mongoose.Types.ObjectId(),
userId:result.userID,
userName:req.body.username,
dob:req.body.dob,
gender:req.body.gender,
photo: req.file? req.file.path : null,
imei:req.body.imei,
});
insertUsers();
}
});
}
});
};
async function insertUsers(){
try{
const id= transaction.insert(myModelSchema1, user);
const id1= transaction.insert(myModelSchema2, userdetils);
const final = await transaction.run();
}
catch(error){
console.error(error);
const rollbackObj = await transaction.rollback().catch(console.error);
transaction.clean();
c
}
}
first when you define your users schema the email must be uniqe wich when fails when you tries to create anothe user document with the same email,
and with this convention you can move forward like this:
const UserLogin=require("../models/userLogin");
const UserDeatil=require("../models/userDetail");
cosnt signup = async (req ,res)=>{
const { email , password ,...details} = req.body
const createdDocs = []
const hashedPwd = hash(password);
try{
const user = new UserLogin({ email , password: hashedPwd });
await user.save()
createdDocs.push(user)
const userDetails = new UserDetails({...details,userId:user._id});
await userDetails.save()
createdDocs.push(userDetails)
catch(err){
res.json({ status:false, message:err.message})
//emulates the rollback when any thing fails on the try flow
if(createdDocs.length){
const operationsToRollBack = createdDocs.map(doc=>doc.remove)
await Promise.all(operationsToRollBack)
}
}
MongoDB supports multi-document transactions starting from version 4.0.
Ideally, if you need a transactional database you would use an SQL type db.
But if you would still like to enjoy MongoDB while needing transactions, they have introduced an API for this - https://docs.mongodb.com/manual/core/transactions/

CRUD on nested schemas using Mongoose

I am trying to set up my nodejs app with a CRUD for mongodb sub-docs using Mongoose but can't figure out how to access the nested object's _id. I can only get the parent ObjectId. I can perform a .push on a new child object but can't perform a simple get, put or delete on an existing child object.
Here is my schema:
//new user model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
// Task schema
var taskSchema = mongoose.Schema({
clientEasyTask : { type: String },
clientHardTask : { type: String },
clientStupidTask : { type: String }
});
var userSchema = new mongoose.Schema({
email: { type: String, unique: true, lowercase: true },
password: String,
task : [taskSchema]
});
module.exports = mongoose.model('Task', taskSchema);
module.exports = mongoose.model('User', userSchema);
Here is my routes:
'use strict';
var isAuthenticated = require('./middleware/auth').isAuthenticated,
isUnauthenticated = require('./middleware/auth').isUnauthenticated;
var User = require('./models/user');
var Task = require('./models/user');
// Create user.task
module.exports = function (app, passport) {
app.post('/api/tasks', isAuthenticated, function (req, res) {
var userEmail = req.body.email;
var easyTask = req.body.easyTask;
User.findOne({ 'email' : userEmail }, function(err, user) {
console.log('found user and defining status and data');
var status;
var data;
if (err) {
status = 'error';
data = 'unknown error occurred';
}
if (user === null) {
status = 'error';
data = 'user not found';
} else {
status = 'ok';
data = user;
}
user.task.push({
clientEasyTask: easyTask
});
user.save();
res.json({
response: {
'status': status
}
});
});
});
// Get one user.task
app.get('/api/tasks/:id', function (req, res) {
return Task.findById(req.params.id, function(err, task) {
if(!task) {
res.statusCode = 404;
return res.send({ error: 'Not found' });
}
if(!err) {
return res.send({ status: 'OK', task:task });
} else {
res.statusCode = 500;
console.log('Internal error(%d): %s', res.statusCode, err.message);
return res.send({ error: 'Server error' });
}
});
});
};
I am using Postman to test everything so there is no fronted code. When I pass the _id of the task (nested in the user) I receive null when I call Get on '/api/tasks/:id'. How can I can get only the specific task?
The mongoose documentation states that you can use parent.children.id(id); but I couldn't get it to work.
The task field of User contains the tasks as embedded subdocs, not references to another collection, so you can't query tasks independent of users (like you're trying to do).
To query for the embedded task subdoc, you can use a query like this:
User.findOne({'task._id': req.params.id})
.select('task.$') // Just include the matching task element
.exec(function(err, user) {
if(!user) {
res.statusCode = 404;
return res.send({ error: 'Not found' });
}
if(!err) {
// The matching task will always be in the first element of the task array
return res.send({ status: 'OK', task: user.task[0] });
} else {
res.statusCode = 500;
console.log('Internal error(%d): %s', res.statusCode, err.message);
return res.send({ error: 'Server error' });
}
}
);
To make this efficient, you'd want to add an index on {'task._id': 1}.

Resources