Avoid using nested promise for validation - javascript

I am using node and mongoDB to create a backend service.
I do have a schema as follows :
const UserSchema = new Schema({
name : {
type : String,
required : true
},
email : {
type : String,
required : true
},
phoneNo : {
type : Number,
required : true
},
password : {
type : String,
required : true
},
avatar : {
type : String
},
date : {
type : Date,
default : Date.now()
}
})
Now I want to validate whether the phone no and email exists or not. For this check I have the following code snippet :
User.findOne({ email : req.body.email })
.then(user => {
if (user){
errors.email = 'Email already exists';
return res.status(400).json(errors);
} else {
User.findOne( {phoneNo : req.body.phoneNo})
.then (user => {
if(user) {
errors.phoneNo = 'Phone no already exists';
return res.status(400).json(errors);
}
else {
.....
So I am using nested promise. I believe this is not a good practice to go on. But I need to show different validation messages for different points. Can this be achievable in any simpler way ?

You could use async/await that will make your code simpler and easier to reason about.
async function main() {
const existingByEmail = await User.findOne({ email: req.body.email });
if (existingByEmail) {
errors.email = "Email already exists";
return res.status(400).json(errors);
}
const existingByPhone = await User.findOne({ phoneNo: req.body.phoneNo });
if (existingByPhone) {
errors.phoneNo = "Phone no already exists";
return res.status(400).json(errors);
}
}

You can set email and phoneNo to be unique (recommended) and check for duplication error handled by mongoose.
email : {
type : String,
required : true,
unique : true // add unique option
},
phoneNo : {
type : Number,
required : true,
unique : true // add unique option
},
This package mongoose-unique-validator will return the field that trigger duplication error. But you can also handle the duplication error by yourself, as suggested here, but you will have to check the index by yourself to see which field caused duplication
Above approaches will handle duplication errors when you insert/update a document, so you won't need your current validation code.
For the purpose of improving your validation flow, if you want to avoid nested Promises, you can call them at the same time using Promise.all()
/* without async/await */
Promise.all([
User.findOne({ email: req.body.email }).exec(),
User.findOne({ phoneNo: req.body.phoneNo }).exec()
]).then(([emailUser, phoneUser]) => {
if (emailUser) {
errors.email = "Email already exists";
return res.status(400).json(errors);
}
if (phoneUser) {
errors.phoneNo = "Phone no already exists";
return res.status(400).json(errors);
}
})
/* with async/await */
const [emailUser, phoneUser] await Promise.all([
User.findOne({ email: req.body.email }).exec(),
User.findOne({ phoneNo: req.body.phoneNo }).exec()
])
if (emailUser) {
errors.email = "Email already exists";
return res.status(400).json(errors);
}
if (phoneUser) {
errors.phoneNo = "Phone no already exists";
return res.status(400).json(errors);
}

Related

How to perform user-input validation using if statements

I'm trying to perform user validation using if statements but on testing using post man, I keep on getting only the first return statement 'Email is required ' even after adding a valid email address and also an invalid email address. I have attached the user model, user controller logic and a picture of postman's response
user schema model
const mongoose = require('mongoose');
const { Schema } = mongoose
const userSchema = new Schema({
firstName: {
type: String,
required: true,
min: 3,
max: 20
},
lastName: {
type: String,
required: true,
min: 3,
max: 20
},
email: {
type: String,
required: true,
unique: true,
},
phoneNumber: {
type: String,
required: true,
},
password: {
type: String,
required: true,
min: 5
},
confirmPassword: {
type: String,
required: true,
min: 5
}
});
const User = mongoose.model('User', userSchema);
module.exports = User;
user.controller.js
module.exports.users = async(req, res) => {
try {
const email = await user.findOne({ email: req.body.email });
const firstName = await user.find({ firstName: req.body.firstName });
const lastName = await user.find({ lastName: req.body.lastName });
const password = await user.find({ password: req.body.password });
const confirmPassword = await user.find({ confirmPassword: req.body.confirmPassword });
const phoneNumber = await user.find({ phoneNumber: req.body.phoneNumber });
if (!email) return res.send('Email is required')
const filterEmail = /^([a-zA-Z0-9_\.\-])+\#(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
if (!filterEmail.test(email.value)) return
res.send('Please provide a valid email address');
if (email) return res.send('Email already exists');
if (!firstName) return res.send('First Name is required');
if (firstName.length < 3 || firstName.length > 20) return
res.send('First name must be at least 3 characters and less than 20 characters');;
if (!lastName) return res.send('Last Name is required');
if (lastName.length < 3 || lastName.length > 20) return
res.send('Last name must be at least 3 characters and less than 20 characters')
if (!password) return res.send('PassWord is required');
const filterPassword = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9])(?!.*\s).{5,15}$/;
if (!filterPassword.test(password.value)) return
res.send('Password must include at least one lowercase letter, one uppercase letter, one digit, and one special character');
if (!confirmPassword) return res.send(' Please confirm password');
if (password.value !== confirmPassword.value) return res.send('Passwords do not match');
if (!phoneNumber) return res.send('Phone Number is required');
phone(phoneNumber.value);
let User = new user(_.pick(req.body, ['firstName', 'lastName', 'email', 'phoneNumber', 'password']));
bcrypt.genSalt(10, async(err, salt) => {
if (err) throw err;
return user.password = await bcrypt.hash(user.password, salt);
});
await User.save();
} catch (err) {
res.status(500).send('Something went wrong');
console.warn(err);
}
}
Your controller is not good. You need to get the email, lastName and firstName from req.body.
const {email, lastName} = req.body
And then do the validation, which by the way will already happen in mongoose.
const email = await user.findOne({ email: req.body.email });
In this line you are looking in your DB for a user with email = req.body.email. But since there is no such a user it return your if statement
if (!email) return res.send('Email is required')
to get your user you only need to compare with one value if you set it to be unique in your schema, for instead user email.
const user = await user.findOne({ email });
In this case email was already destructed and if there is no user, you can create one.
you can use await user.create() and pass your values and hash the password with pre("save") in your schema.
UserSchema.pre("save", async function () {
if (!this.isModified("password")) return;
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
You can also validate user email in the schema
email: {
type: String,
required: [true, "Please provide email"],
match: [
/^(([^<>()[\]\\.,;:\s#"]+(\.[^<>()[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
"Please provide a valid email",
],
unique: true,
},
You can simply install the validator package npm i validator then call it in in the user model const { isEmail } = require('validator'); and finally type this in your schema instead of hardcoding the verification code in the function.
email: { type: String, required: true, validate: [isEmail], unique: true },
Instead of writing custom logic for validation Please add a validation layer after performing security checks
you can use Express validator, to perform all your validations in a smoother way, It will eliminate unnecessary logic and code lines and add structured code to validate all types inputs, nested objects, DB call for existing data handling. It will handle the response and response message on its own without hitting the controller
just hit
npm install --save express-validator
and you are ready to validate requests very easily and effectively
Beside other answers i thought one thing which is missing but yet it is very strong validation method in Node.JS is JOI package, you can define your validation schema and get rid of all other pain full if and else, example is following
const Joi = require('joi');
const schema = Joi.object().keys({
firstName: Joi.string().alphanum().min(3).max(20).required(),
lastName: Joi.string().alphanum().min(3).max(20).required(),
email: Joi.email().required(),
phoneNumber: Joi.string().length(10).pattern(/^[0-9]+$/).required(),
password: Joi.string().min(5).required(),
confirmPassword: Joi.any().valid(Joi.ref('password')).required()
});
const dataToValidate = {
firstName: 'chris',
lastName: 'John',
email: 'test#test.com'
}
const result = Joi.validate(dataToValidate, schema);
// result.error == null means valid
Without node script you can always test your JOI schema and your data object on this webpage

Why find() using models won't work in signup route?

It's a simple signup route to store credentials in a mongoDB database but I miss something because the 2 else if won't work properly. I suspect it is my find().
The first else if returns me in Postman "error": "E11000 duplicate key error collection: vinted.users index: email_1 dup key: { email: \"jean#dpont.com\" }" and the second give me "email already exists".
Thanks in advance for your help
const express = require("express");
const router = express.Router();
const SHA256 = require("crypto-js/sha256");
const encBase64 = require("crypto-js/enc-base64");
const uid2 = require("uid2");
const User = require("../models/User");
router.post("/user/signup", async (req, res) => {
try {
const email = req.fields.email;
const username = req.fields.username;
const phone = req.fields.phone;
const password = req.fields.password;
const token = uid2(64);
const salt = uid2(16);
const hash = SHA256(password + salt).toString(encBase64);
const emailSearch = await User.find({ email: email });
if (!emailSearch || username !== null) {
const newUser = new User({
email: email,
account: {
username: username,
phone: phone,
},
password: password,
token: token,
hash: hash,
salt: salt,
});
await newUser.save();
res.status(200).json({
_id: newUser._id,
token: newUser.token,
account: newUser.account,
});
}
//problem under
else if (emailSearch) {
res.status(404).json({ message: "email already exists" });
} else if (username === null) {
res.status(404).json({ message: "please type a username" });
}
} catch (error) {
res.status(404).json({
error: error.message,
});
}
});
It looks like the issue is that if the username in the request body is not null, it's going to attempt to create a new User with that username regardless of whether a User exists with the same email - if (!emailSearch || username !== null).
It's generally best-practice to do as much input validation as you can before you start looking for records or creating new ones, as you will be able to avoid more Mongo errors and database actions if you can stop invalid actions before they're attempted. So in this case, check that the username is valid before looking for existing Users.
To solve this problem, I would move that last else-if to before you check whether a User exists with the same email. That way, once you determine whether the username is valid, then the only thing that you need to consider is existing Users before creating a new one. Something like this:
if (username === null) {
res.status(400).send({ message: "Error: Please provide a 'username'." });
}
const existingUserWithEmail = await User.find({ email: email });
if (!existingUserWithEmail) {
// Create the new User
} else {
res.status(400).send({ message: "Error: An account already exists with this email." });
}

Invalid prisma.user.findUnique() invocation

I can't seem to find what the error in the invocation is
function findUser(req, res) {
const username = req.body.username;
prisma.user.findUnique({
where: { username: username },
select: { username: true }
})
.then(data => {
res.send({
'userExists': data ? true : false
})
})
.catch(err => {
res.status(500).send({
message: err.message || "Some error occurred while retrieving user."
})
})
.finally(async () => { await prisma.$disconnect()});
// schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int #default(autoincrement()) #id
username String #unique
password String
salt String
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
From Prisma side everything is OK. The problem is probably req.body.username, if it's undefined you receive Invalid 'prisma.user.findUnique()' invocation.
You have to add validation for username, i.e.
if {typeof username !== string} return res.status(404).send('invalid username')
It might be the late response. I was also running into the same problem. But documentation saved my day. https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#findunique
You just have to add #unique attribute to your username field in schema.prisma, like below —
username String #unique #db.VarChar(255)
After looking at your code it seems that the username is missing from your req.body. I would suggest always verify the params you want to extract from req.body. I refactor your code to es6.
Here is the updated code you can try,
function findUser(req, res) {
// Destructure username from req.body
const { username } = req.body;
if(username == null) throw new Error('Username undefined');
// when property and value is same, you can write 'username' like below
prisma.user.findUnique({
where: { username },
select: { username: true }
})
.then(data => {
res.send({
'userExists': data ? true : false
})
})
.catch(err => {
res.status(500).send({
message: err.message || "Some error occurred while retrieving user."
})
})
.finally(async () => { await prisma.$disconnect()});

How can I validate this in more elegant way?

Im trying to do login/register module in my project. This is my login function. I would like to have one function that will validate all things for me so I dont have to use so many "if" statements. I was trying to do with pure function but completely don't know how to do it. Can someone help me ?
const loginUser = async (req, res, next) => {
const { password, email } = req.body;
if (!email) {
return res.status(400).json({
message: "Error: Email cannot be blank.",
});
}
if (!password) {
return res.status(400).json({
message: "Error: Password cannot be blank.",
});
}
try {
const user = await User.findOne({ email: email });
if (!user)
return res.status(400).json({
message: "Invalid user",
});
if (!validPassword(password, user.password))
return res.status(400).json({
message: "Invalid password",
});
const { name, likedArr, _id } = user;
const token = crypto.randomBytes(32).toString("hex");
const userSession = new UserSession({ userId: _id, token });
await userSession.save();
return res.status(200).json({
message: "Valid login",
token: token,
user: {
name,
likedArr,
userId: _id,
},
});
} catch (err) {
next(err);
}
};
Abstracting my comments into an answer.
On Pure Functions:
If I understand pure functions correctly, I don't think you can have a pure function that calls an external API which may fail, since the same inputs may possibly return different results depending on the external state of the API (unless that API is guaranteed pure itself somehow). (Definition of a pure function)
On Repetition:
I genuinely think you don't have a lot of repetition here. Your code is clear and only has 4 conditionals, all for things you need to test for. You could abstract the similarities of your JSON returns into something like a template string depending on the conditions, but I think that could add clutter and opacity to your code, which isn't a good trade-off if you do it too much.
If you want an example of what I mean here:
if (!email) {
return res.status(400).json({
message: "Error: Email cannot be blank.",
});
}
if (!password) {
return res.status(400).json({
message: "Error: Password cannot be blank.",
});
}
Can become...
if (!email || !password) {
return res.status(400).json({
message: `Error: ${!email ? 'Email' : 'Password'} cannot be blank.`,
});
}

getting isEmail of undefined error when creating registration system in nodejs

Sorry I'm new to javascript and nodejs, but basically I am trying to create a registration system. I've attached my form validator and my user schema, but I keep getting an isEmail of undefined error. I'm not sure if there's an easier way to go about checking these conditions. Earlier, I was using req.checkBody, but I wasn't sure how to use that to check if a certain user is above a given age.
router.post('/register', function(req, res, next) {
var name = req.body.name;
var email = req.body.email;
var password = req.body.password;
var password2 = req.body.password2;
var bday = req.body.bday;
var birthday = moment(bday);
let errors = [];
// Form Validator
if (!name || !email || !password || !password2 || !bday) {
errors.push({ msg: 'Please fill in all required fields' });
}
if (!req.body.email.isEmail()) {
errors.push({ msg: 'Please provide an appropriate email' });
}
if (req.body.password2 != req.body.password) {
errors.push({ msg: 'Please make sure your passwords match' });
}
if (!birthday.isValid()) {
errors.push({ msg: 'Date of Birth must be in appropriate format' });
}
if (moment().diff(birthday, 'years') < 13) {
errors.push({ msg: 'User must be at least 13 years of age' });
}
if(errors.length > 0){
res.render('register', {
errors,
name,
email,
password,
password2,
bday
});
} else{
var newUser = new User({
name: name,
email: email,
password: password,
bday: bday,
});
var UserSchema = mongoose.Schema({
password: {
type: String
},
email: {
type: String
},
name: {
type: String
},
bday: {
type: String
}
});
When you look at this here
if (!req.body.email.isEmail()) {
errors.push({ msg: 'Please provide an appropriate email' });
}
You are trying to access the isEmail properties from email which must've been non existent.
What the req might have looked like: { req: { body: {} } }, as you can see email must be undefined and undefined does not contain isEmail property.
What you can do is
if (req.body.email && !req.body.email.isEmail()) {
errors.push({ msg: 'Please provide an appropriate email' });
}
Check first if req.body.email exists before trying to access isEmail.

Categories