mongoose Model.create function returns undefined - node.js

The above query returns a 200 when I try to create a User, but whenever I log into MongoDB there is no collections created. Can anyone help ?
//user model
const userSchema = mongoose.Schema({
name: {
type : String,
required : true,
trim : true
},
email: {
type: String,
required: true,
unique: true,
lowercase: true,
validate: value => {
if(!validator.isEmail(value)){
throw new Error({error : 'Invalid email address'})
}
}
},
password: {
type: String,
required: true,
minLength: 5
},
// a user can have multiple jobs
jobs : [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Job'
}],
tokens: [{
token: {
type: String,
required: true
}
}]
})
const User = mongoose.model('User', userSchema)
module.exports = User
// user functions written
createUser(name, email, password){
return User.create({name: name, email: email, password : password}, (err, docs) => {
if(err){
throw err.message;
}
});
}
//routes.js
// user create
router.post('/users', async(req, res) => {
try{
const {name, email, password } = req.body
const user = userManager.createUser(name, email, password); [1]
res.status(200).json(user)
}
catch(error) {
res.status(400).send({error : error.message})
}
})
The line[1] returns undefined. Why ?
note : all module requirements are fulfilled

After you create the schema you need to create a Model FROM that schema.
Example from MDN:
// Define schema
var Schema = mongoose.Schema;
var SomeModelSchema = new Schema({
a_string: String,
a_date: Date
});
// Compile model from schema
var SomeModel = mongoose.model('SomeModel', SomeModelSchema );
Now after you create the model you can use SomeModel.create
EDIT:
line[1] will always return undefined because you are using callbacks and only way to get value out of callback is either push another callback(I would really discourage that). But best way is to use Promises now mongoose by default supports `Promises. So, basically for promises it will be,
// user functions written
async function createUser(name, email, password){
try {
return await User.create({ name: name, email: email, password: password });
} catch (err) {
throw err.message;
}
}
In the router adda await:
const user = await userManager.createUser(name, email, password);

The problem is you call an asynchronous function synchronously. It returned undefined because the function hasn't been resolved yet.
A solution could be to use promises or async/await.
Example:
async createUser(name, email, password) {
const createdUser = await User.create({name,email,password});
return creaatedUser;
}

Something I ran into was you need to pass in an empty object if your not setting any fields - i.e.
Good: Model.create({})
Bad: Model.create()

Related

How next() works here?

i just do password encryption before saving it in db, i just want to know how next() works here?
i know that next() helps us to jump one middleware to next middleware.
userModel.js
....
....
//below will encrypt password before saving user in db
//password is the field of documents og mongodb
userSchema.pre("save", async function () {
console.log(this.password);
if (!this.isModified("password")) {
next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
....
....
//full code userModel.js
const mongoose=require("mongoose");
const bcrypt=require("bcryptjs");
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique:true },
password: { type: String, required: true },
pic: {
type: String,
default:
"https://icon-library.com/images/anonymous-avatar-icon/anonymous-avatar-icon-25.jpg",
},
},
{
timestamps:true,
});
//below compare given password with before password
userSchema.methods.matchPassword=async function(enteredPassword){
console.log("this.password",this.password)
return await bcrypt.compare(enteredPassword,this.password)
}
//below will encrypt password before saving user in db
userSchema.pre("save", async function () {
console.log(this.password);
if (!this.isModified("password")) {
next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
//mongoose.model(<Collectionname>, <CollectionSchema>)
//generally const "User" and "User" in mongoose.model() written in same name
const User=new mongoose.model("User",userSchema);
module.exports=User;
I am new in node js please help me to understand middleware
You can see more about middleware in mongoose here:
How mongoose middleware works and what is next()?
In short, next() will tell mongoose you are done and to continue with the next step in the execution chain. In your example, it will execute the "save" command to save the password to database if the password is not modified and go to next callback (if has).

Mongoose user.save() doesn't return updated _id

I have a simple code that uses mongoose with nodejs to save a user object but when I log the result object that is return from save() method it is just the object I sent and the _id and id values are undefined.
Still, the object is saved correctly in the database with the new _id.
this is my code
`
import { mongoose } from "mongoose";
const Schema = mongoose.Schema;
const userSchema = new Schema({
name: {
type: String,
required: true,
},
age: {
type: Number,
required: true,
},
email: {
type: String,
required: true,
},
});
export const addUser = async (_user) => {
let user = new User({ ..._user });
let result;
try {
result = await user.save();
console.log(result._id);
} catch (err) {
console.log(err);
}
return result;
};
`
I expect console.log(result._id) to have the new generated Id but it is undefined even though it is saved correctly to the database
The solution that worked for me is to assign the _id before calling save()
export const addUser = async (_user) => {
let user = new User({ ..._user });
user._id = mongoose.Types.ObjectId();
let result;
try {
result = await user.save();
console.log(result._id);
} catch (err) {
console.log(err);
}
return result;
};

How to apply mongoose middleware pre on findOneAndUpdate method to validate type of data?

When I try to update or findOneAndUpdate it only validates wether any required are missing, but it doesn´t validate the data type. At the moment I can update the name property to a number and no validation error happens. Any idea on what to do?
mongoose.set('runValidators', true);
const mongoose = require("mongoose")
const { Schema } = mongoose;
const UserSchema = new Schema ({
nome: {
type: String,
required: true,
trim: true
},
email: {
type: String,
required: true,
trim: true
},
morada: {
type: String,
required: true,
trim: true
}
});
const User = mongoose.model('User', UserSchema);
module.exports = User
const update = async (req, res) => {
try {
let post = await Post.findOneAndUpdate(req.params, req.body, {new: true});
res.json(post)
} catch (e) {
res.status(500).json(e)
}
}
As I have read here I could use the pre middleware to validate data type, but I´m having trouble with how to use it to update:
Post.pre("save", function(next, done) {
let self = this;
if (invalid) { // This is where I think I should validate the data type, but don´t know how to
// Throw an Error
self.invalidate("nome", "name must be a string");
next(new Error("nome must be a string"));
}
next();
});
Any suggestions? Thanks on advance

Why is the reference not being saved along with the rest of the data?

I am new with MongoDB "relations" and I am trying to save data to a MongoDB database. There are two models, one model is the user and the other model is the authentication data. The data is saved correctly.
import { Schema, model } from 'mongoose'
const stringRequired = {
type: String,
trim: true,
required: true
}
const stringUnique = {
...stringRequired,
unique: true
}
const UserSchema = new Schema({
name: stringRequired,
username: stringUnique,
email: stringUnique,
}, { timestamps: true });
const AuthSchema = new Schema({
email: { type: Schema.Types.ObjectId, ref: 'User' },
salt: stringRequired,
hash: stringRequired,
}, { timestamps: true })
export const userModel = model('User', UserSchema)
export const authModel = model('Auth', AuthSchema)
As you can see, one of the models is referenced by another. The email field has a reference to the user, email being the id that I want to use for authentication. But for some reason, when I save the documents, all the data is sent except the reference.
This is my controller, which as you can see, abstracts the user and the authentication to carry out the business logic and then save it in the database separately.
function add(body: any) {
return new Promise((resolve, reject) => {
if (!body) {
const error = new Error('No body on the request')
reject(error)
} else {
const user = {
username: body.username,
email: body.email,
name: body.name
}
const saltRouds = 10
const salt = bcrypt.genSaltSync(saltRouds)
const hash = bcrypt.hashSync(body.password, salt)
const auth = { salt, hash }
store.add(userModel, user)
store.add(authModel, auth)
resolve('User created')
}
})
}
This is the store.add function.
async function add (collection: any, data: any) {
return await collection.create(data)
}
Note this code is writhed with Typescript.
You're missing the reference key when creating the Auth instance. The "foreign key" in MongoDB is the id of a document that has type Schema.Types.ObjectId and can be accessed with document._id.
So your code should look like:
const auth = { salt, hash };
const user = store.add(userModel, user);
auth.email = user._id;
store.add(authModel, auth);
Be aware that your store.add() function is an async function and you should wait for it's result like #jfriend00 said in the comments.
You can achieve that by making add() also an async funtion and doing:
const auth = { salt, hash };
const user = await store.add(userModel, user);
auth.email = user._id;
await store.add(authModel, auth);
or using the Promise approach by chaining .then(). You can read more about it here.

How to show Errors from Mongoose?

I have a user I can save in MongoDB, when I enter correct data, the save works.
But when I enter wrong data, I can't catch the errors to be seen for the user. All I can see is this on the code editor:
...UnhandledPromiseRejectionWarning: ValidationError: User validation
failed: username: username is not there!...
This error "kills" the server, and so I'm not rendering home-guest template.
The question is how I can catch the errors and show them to the user?
Schema:
const mongoose = require("mongoose")
const userSchema = new mongoose.Schema({
username: {
type: String,
required: [true, "username is not there!"],
minlength: 3,
maxlength: 20,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
minlength: 6,
maxlength: 20,
},
})
module.exports = mongoose.model("User", userSchema)
Controller:
const mongoose = require("mongoose")
const userModel = require("../models/userModel")
exports.signUp = async (req, res) => {
const { username, email, password } = req.body
try {
const user = await new userModel({
username,
email,
password,
})
user.save()
} catch (error) {
res.render("home-guest", { error })
}
}
You just need to add an await to the save operation, since that's also async:
const mongoose = require("mongoose")
const userModel = require("../models/userModel")
exports.signUp = async (req, res) => {
const { username, email, password } = req.body
try {
const user = await new userModel({
username,
email,
password,
})
// Wait for the save to complete, also allowing you to catch errors
await user.save()
} catch (error) {
res.render("home-guest", { error })
}
}
EDIT: And note that you do not need an async in front of new userModel(). new cannot return a promise, it is always synchronous.

Resources