How to show Errors from Mongoose? - node.js

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.

Related

How do I write truly custom error messages and codes for Mongoose validation?

I'm trying to follow the MVC architectural pattern and do all of my validation in my Mongoose model, rather than my controller.
I'm wondering how I can set error codes and truly custom error messages in my model (I.E. without the part that mongoose adds to the beginning of the message.)
At the moment my error message for the name field is: "message": "User validation failed: email: Please enter a valid email address", where it should be "Please enter a valid email address".
The response code from the server was 200 until I changed it in my errorHandlerMiddleware file, which is not ideal as it should be a 400 not the general 500.
So, somebody please help me to set the status code in my model and also make a custom error message.
Many thanks in advance!
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const validator = require("validator");
const Schema = mongoose.Schema;
const UserSchema = new Schema(
{
name: {
type: String,
required: [true, "Please add a name"],
minLength: [3, "Name must be at least 3 characters"],
},
email: {
type: String,
required: [true, "Please add an email address"],
unique: [true, "It looks like you already have an account!"],
validate: {
validator: (value) => {
if (!validator.isEmail(value)) {
throw new Error("Please enter a valid email address");
}
},
},
},
password: {
type: String,
required: [true, "Please add a password"],
},
tokens: [
{
token: {
type: String,
required: true,
},
},
],
},
{ timestamps: true }
);
UserSchema.methods.toJSON = function () {
const user = this;
const userObject = user.toObject();
delete userObject.password;
delete userObject.tokens;
return userObject;
};
UserSchema.methods.generateAuthToken = async function () {
const user = this;
const token = jwt.sign({ _id: user._id.toString() }, process.env.JWT_SECRET, {
expiresIn: "7 days",
});
user.tokens = user.tokens.concat({ token });
await user.save();
return token;
};
UserSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({ email });
if (!user) {
statusCode(401);
throw new Error("Unable to login");
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
statusCode(401);
throw new Error("Unable to login");
}
return user;
};
UserSchema.pre("save", function (next) {
if (this.password.length < 6) {
throw new Error("Password must be at least 6 characters");
}
if (!this.isModified("password")) {
return next();
}
this.password = bcrypt.hashSync(this.password, 10);
return next();
});
module.exports = User = mongoose.model("User", UserSchema);
i need a real custom error code and message from mongoose
I decided to catch the errors in the try/catch block on the controller, as so:
try {
await user.save();
} catch (err) {
// Error handling for duplicate email address
if (err.code === 11000) {
return res.status(400).send("It looks like you already have an account.");
}
// Error handling for misc validation errors
if (err.name === "ValidationError") {
res.status(400);
return res.send(Object.values(err.errors)[0].message);
}
}

I'm not sure how to delete a user using MongoDB

I'm trying to be able to delete a user if they choose to delete their account. I'm not sure how it's properly done. Here's the code for the delete function. I've looked at other StackOverflow solutions but I haven't been able to grasp the concept just yet. can anyone help?
const { validationResult } = require('express-validator')
const User = require('../modelSchema/User')
const mongo = require('mongodb')
const signUp = (req, res) => {
const errors = validationResult(req)
if(!errors.isEmpty()) return res.status(400).json({ errors: errors.array()})
//new instance of user
const user = new User({
name: req.body.name,
email: req.body.email,
password: req.body.password })
//save the user
user.save()
.then(data => {
res.json(data)
res.send('User added successfully!')
console.log(data)
})
.catch(err => {
res.status(400).json(err)
})
}
const deleteUser = (req, res) => {
let id = req.params.id;
User.get().createCollection('user', function ( col) {
col.deleteOne({_id: new mongo.ObjectId(id)});
});
res.json({ success: id })
res.send('User Deleted Successfully!')
.catch(err => {
res.status(400).json(err)
})
}
module.exports = { signUp, deleteUser }
here are the routes that I'm using
const express = require('express');
const { check } = require('express-validator')
const { signUp, deleteUser } = require('../controllers/users');
const router = express.Router();
router.post('/signup', [
check('name', 'Please Enter your Name').not().isEmpty(),
check('email', 'Please Enter your Email').isEmail(),
check('password', 'Please Enter your Password').isLength({ minLength: 6})
], signUp)
router.delete('/delete/:id', deleteUser)
module.exports = router;
and here's my schema
const mongoose = require('mongoose');
let userSchema = new mongoose.Schema({
name: { type: 'string', required: true},
email: { type: 'string', required: true},
password: { type: 'string', required: true},
date: { type: Date, default: Date.now}
})
module.exports = mongoose.model('user', userSchema)
Update your delete function as below
const deleteUser = async (req, res) => {
const { id } = req.params;
const user = await User.findByIdAndDelete(id);
if (!user) {
return res.status(400).json("User not found");
}
res.status(200).json("User deleted successfully");
};
findByIdAndDelete takes an ObjectId of a user to be deleted. If the user exist, it'll delete the user and return user document, else return null;

Unable to add properties in a MongoDB document

I am trying to implement password reset functionality in a MERN application. Whenever a user enters their email (for which they want to reset the password) and clicks on the "Send Password Reset Link" button, a POST request is made to the route "/account/forgot".
In the route handler function, I check whether any user with the given email exists or not. If a user exists, then I add resetPasswordLink and resetPasswordExpires properties to the user object and send a message "You have been emailed a password link" to the client.
The problem I am facing is I get the message at the frontend.
However, whenever I check the database, I don't see resetPasswordLink and resetPassworExpires properties being added to the user.
Where is the problem?
The code snippets are given below:
server/routes/passwordResetRoutes.js
const express = require("express");
const crypto = require("crypto");
const asyncHandler = require("express-async-handler");
const User = require("../models/userModel");
const router = express.Router();
router.post(
"/forgot",
asyncHandler(async (req, res, next) => {
const user = await User.findOne({ email: req.body.email });
if (user) {
user.passwordResetToken = crypto.randomBytes(20).toString("hex");
user.passwordResetExpires = Date.now() + 3600000;
await user.save();
res.json({
message: "You have been emailed a password reset link",
});
} else {
const err = new Error("No account with that email exists");
err.status = 404;
next(err);
}
})
);
module.exports = router;
server/models/userModel.js
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const userSchema = new mongoose.Schema({
firstName: {
type: String,
required: true,
},
lastName: {
type: String,
required: true,
},
email: {
type: String,
unique: true,
required: true,
},
password: {
type: String,
required: true,
},
resetPasswordToken: {
type: String,
},
resetPasswordExpires: {
type: Date,
},
});
userSchema.methods.matchPassword = async function (incomingPassword) {
return await bcrypt.compare(incomingPassword, this.password);
};
userSchema.pre("save", async function (next) {
if (!this.isModified("password")) {
next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
const User = mongoose.model("User", userSchema);
module.exports = User;
You're trying to update the passwordResetToken and passwordResetExpires fields but they are not present in the User model. That's why the user.save() call does nothing. They should be resetPasswordToken and resetPasswordExpires respectively.
user.resetPasswordToken = crypto.randomBytes(20).toString('hex')
user.resetPasswordExpires = Date.now() + 3600000
await user.save()

Trying to add new user to database (MongoDB) getting this error

I am trying to save a user to MongoDB database using post request as follow, but I got the error TypeError: User is not a function. I can't figure out anything wrong with it. The output says that error is present on the line " const user = new User(req.body);"
postman output nodejs output
is my userschema wrong or the export method is wrong.
const User = require("../models/user");
exports.signup = (req, res) => {
const user = new User(req.body);
user.save((err, user) => {
if (err) {
return res.status(400).json({
err: "NOT able to save user in DB"
});
}
res.json({
name: user.name,
email: user.email,
id: user._id
});
});
};
exports.signout = (req, res) => {
res.json({
message: "User signout"
});
};
//user schema
var mongoose = require("mongoose");
const crypto = require('crypto');
const uuidv1 = require('uuid/v1');
var userSchema = new mongoose.Schema({
name:{
type:String,
required:true,
maxlenght:32,
trim: true
},
lastname:{
type: String,
maxlenght:32,
trim: true
},
email:{
type: String,
trim: true,
required: true,
unique:true
},
userinfo:{
type:String,
trim:true
},
encry_password:{
type:String,
required: true,
},
salt:String,
role:{
type:Number,
default:0,
},
purchases :{
type:Array,
default:[]
}
} ,{ timestamps: true } );
userSchema.virtual("password")
.set(function(password){
this._password = password;
this.salt = uuidv1();
this.encry_password = this.securePassword(password);
})
.get(function(){
return this._password;
})
userSchema.methods = {
authenticate: function(plainpassword){
return this.securePassword(plainpassword) === this.encry_password;
},
securePassword: function (plainpassword){
if(!plainpassword) return "";
try {
return crypto.createHmac('sha256',this.salt)
.update(plainpassword)
.digest('hex');
} catch (err) {
return "";
}
}
};
module.export = mongoose.model("User",userSchema)
Here is the issue with the code.
Line 1: const user = new User(req.body);
Line 2: user.save((err, user) => {
JS is now confused about user which is a constant variable to that of the user which is the return value of the save action.
So, to get rid of this, rename the return value of save action to something else like responseUserObj. Thus your above two lines of code should now be
const user = new User(req.body);
user.save((err, responseUserObj) => {
Happy coding.

mongoose Model.create function returns undefined

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()

Resources