Mongoose query promise in function make code became ugly - javascript

I was creating my users API, I want to check if username had been used.
So I wrote a static function
static findByName(name) {
const query = User.where({
username: name,
});
query.findOne((queryErr, user) => {
if (queryErr) {
console.log(queryErr);
return false;
}
return user;
});
}
when I called it in signUp
signup(req, res) {
if (!req.body.username || !req.body.password || !req.body.email) {
return res.status(400).json({ success: false, message: 'Bad Request' });
}
if (!Users.findByName(req.body.username)) {
return res.status(409).json({ success: false, message: 'Username has been used' });
}
const hashedPassword = this.genHash(req.body.password);
const newUser = User({
username: req.body.username,
});
}
findByName return Undefined.
Finally I use promise.
signup(req, res) {
if (!req.body.username || !req.body.password || !req.body.email) {
return res.status(400).json({ success: false, message: 'Bad Request' });
}
return Users.findByName(req.body.username).then((existingUser) => {
if (existingUser) {
return res.status(409).json({ success: false, message: 'Username has been used' });
}
const hashedPassword = this.genHash(req.body.password);
const newUser = User({
username: req.body.username,
password: hashedPassword,
email: req.body.email,
});
return newUser.save().then((user) => {
res.json({ success: true, user });
}).catch((err) => {
res.status(500).json({ success: false, message: 'Internal Server Error' });
});
}).catch((err) => {
res.status(500).json({ success: false, message: 'Internal Server Error' });
});
}
That is really horrible code.
Is there better way to clean the code?

Is there better way to clean the code
Yes. I am going to assume /signup is defined as a POST route on the usual Express app instance
With that said you, since you are already using promises, you can go a step further and use async/await which is enabled by default in Node.js v7.6+.
This will make your code read more synchronously:
async signup(req, res) {
if (!req.body.username || !req.body.password || !req.body.email) {
return res.status(400).json({ success: false, message: 'Bad Request' });
}
try {
const existingUser = await Users.findByName(req.body.username)
if (existingUser) {
return res.status(409).json({ success: false, message: 'Username has been used' })
}
const hashedPassword = this.genHash(req.body.password);
const newUser = await User({
username: req.body.username,
password: hashedPassword,
email: req.body.email,
}).save()
res.json({ success: true, newUser });
} catch (error) {
res.status(500).json({ success: false, message: 'Internal Server Error' });
}
}
You may have noticed the use of try/catch. This is because since we are not using .catch() and we still have to handle any error that occurs. To further clean up to code, we can write an error handler middleware that will take care of errors for us:
src/middleware/error-handlers.js
// Wraps the router handler, catches any errors, and forwards to the next middleware that handles errors
exports.catchErrors = action => (req, res, next) => action(req, res).catch(next);
// Notice the first parameter is `error`, which means it handles errors.
exports.displayErrors = (error, req, res, next) => {
const err = error;
const status = err.status || 500;
delete err.status;
err.message = err.message || 'Something went wrong.';
if (process.env.NODE_ENV === 'production') {
delete err.stack;
} else {
err.stack = err.stack || '';
}
res.status(status).json({
status,
error: {
message: err.message,
},
});
};
Now we just need to use our error handlers:
app.js
const { catchErrors, displayErrors } = require('./middleware/error-handlers')
// Whenever you defined the function, needs to have the `async` keyword
async function signup(req, res) { ... }
// Wrap the function call
app.post('/signup', catchErrors(signUp))
// Handle any errors
app.use(displayErrors)
Using the above middleware transforms our code to:
async signup(req, res) {
const error = new Error()
if (!req.body.username || !req.body.password || !req.body.email) {
error.status = 400
error.message = 'Bad Request'
throw error
}
const existingUser = await Users.findByName(req.body.username)
if (existingUser) {
error.status = 409
error.message = 'Username has been used'
throw error
}
const hashedPassword = this.genHash(req.body.password);
const newUser = await User({
username: req.body.username,
password: hashedPassword,
email: req.body.email,
}).save()
res.json({ success: true, newUser });
}
You can see how the code is much easier to read without all the noise.
Be sure to read up on:
Writing middleware for use in Express apps
Express error handling

I can't test that my solution actually works (probably not) or that I'm covering every functionality of your original code. What I tried to do is to make functions that handle specific things, then chain them together, so that you can see a more clear flow of the program.
signup(req, res) {
if (!req.body.username || !req.body.password || !req.body.email) {
return res.status(400).json({ success: false, message: 'Bad Request' });
}
const handleError = error => res.status(500).json({ success: false, message: 'Internal Server Error' });
const handleExistingUser = existingUser => {
if (existingUser) {
return res.status(409).json({ success: false, message: 'Username has been used' });
} else {
return Promise.resolve();
}
}
const handleCreateUser = () => {
const hashedPassword = this.genHash(req.body.password);
const newUser = User({
username: req.body.username,
password: hashedPassword,
email: req.body.email,
});
return newUser.save().then((user) => {
res.json({ success: true, user });
});
};
// the flow of the program is hopefully more clear here
return Users.findByName(req.body.username)
.then(handleExistingUser)
.then(handleCreateUser)
.catch(handleError);
}
If you handle the error of the inner and outer promise the same, then I think it's enough to have the error handling in the outer layer. (In your last example.) But I'm not 100% certain.

Your function returns undefined, because it does not have a return statement. The return user statement, is the (useless) return value for the findOne callback function, not for findByName.
If you go for promises, then define the function as follows:
static findByName(name) {
return User.where({ username: name }).findOne().exec();
}
And your promise chain can be simplified a bit, like this:
signup(req, res) {
function report(message, status = 200) {
res.status(status).json({ success: status === 200, message });
}
if (!req.body.username || !req.body.password || !req.body.email) {
return report('Bad Request', 400);
}
Users.findByName(req.body.username).then((existingUser) => {
return existingUser ? null // treat this condition in the next `then`
: User({
username: req.body.username,
password: this.genHash(req.body.password),
email: req.body.email,
}).save().exec();
}).then((user) => {
return existingUser ? report(user)
: report('Username has been used', 409);
}).catch((err) => {
report('Internal Server Error', 500);
});
}

Related

Express + TSOA + Passport

I am new to Passport and to TSOA. I am trying to migrate an old Express route to TSOA controllers but I have not any idea how to achieve this. I did not find any documentation regarding this, in fact, I found this issue but I think I need a workaround.
router.post(
'/register',
(req: Request, res: Response, next) => {
const user: IUserData = req.body
if (validator.isEmpty(email))
return res.status(422)
.json({ error: 'Email is mandatory' })
if (validator.isEmpty(password.trim()))
return res.status(422)
.json({ error: 'Password is mandatory' })
next()
},
passport.authenticate('local'),
async (req: Request, res: Response) => {
if (req.user)
return res.json({ message: 'Already logged in' })
const existingUser: IUser = await UserModel.findOne({ email: req.body.email })
if (existingUser)
return res.status(400)
.json({ error: 'User exists' })
const data: IUserData = req.body
try {
const hashedPassword = await bcrypt.hashSync(data.password, parseInt(process.env.SALT_ROUNDS) || 10)
data.password = hashedPassword
} catch(error) {
return res.status(500)
.json({ error: error.message })
}
try {
const user: IUser = await new User(data)
await user.save()
res.json(user);
} catch(error) {
return res.status(500)
.json({ error: error.message });
}
return;
}
);
I have tried to "replicate" this code into a TSOA controller but I don't know how to apply the middleware stuff:
#Route('/auth')
export class AuthController extends Controller {
#Post()
public async register(#BodyProp() user: IUserData) {
const errors: Array<IAuthError> = []
if (validator.isEmpty(user.emailAddress)) {
this.setStatus(422)
return { error: 'Email is mandatory' }
}
if (validator.isEmpty(user.password.trim())) {
this.setStatus(422)
return { error: 'Password is mandatory' }
}
}
}
// to be continued ...
I could also separate this certain route in a different file (without any controller) but I don't know if that would work at all. I guess not.
How should I manage this?
Edit:
I just have read this article regarding TSOA authentication and it seems to handle authentication by using middlewares.

Getting error while saving documents using mongoose in node express.js project

I am getting error even after saving document to the mongodb using mongoose in node express.js project.
Here's my code:
exports.storeJob = async (req, res, next) => {
const { name, email, password, title, location, descriptionUrl, tags, company, companyLogo, coupon, showLogo, highlightWithColor, customColor, makeSticky } = req.body;
const { error } = userRegisterValidation(req.body);
if (error) return res.status(400).json({ success: false, message: error.details[0].message });
const emailExists = await User.findOne({ email: email });
if (emailExists) return res.status(400).json({ success: false, message: "User already exits. Please Login" });
const salt = await bcrypt.genSalt(10);
const hashPassword = await bcrypt.hash(password, salt);
const user = new User({
name: name,
email: email,
password: hashPassword
});
// try{
const savedUser = await user.save();
const job = new Job({
title,
location,
descriptionUrl,
tags,
company,
companyLogo,
coupon,
showLogo,
highlightWithColor,
customColor,
makeSticky,
status: 'open',
user: savedUser
});
try {
const createdJob = await job.save();
// try {
user.jobs.push(createdJob);
user.save();
res.status(201).json({ success: true, data: savedUser });
// } catch {
// res.status(400).json({ success: false, message: "Some error occured" });
// }
} catch (err) {
res.status(400).json({ success: false, message: "Error while creating job.", error: err });
}
// } catch(err) {
// res.status(400).json({ success: false, message: "Error while creating user" });
// }
}
I have 2 questions:
I have register method in userController. Is there any way to use that method inside storeJob method?
In the above code even after saving user and job to the database and linking them api response is
{ success: false, message: "Error while creating job.", error: {} }
user.jobs.push(createdJob);
user.save();
In that case, this two lines creates a new user, because user defines the User schema.
Instead of these two lines try this
var push = {
jobs:createdJob
}
var update = {
"$addToSet":push
}
await User.findOneAndUpdate({ "_id": savedUser._id },update).exec();
Hope it will work fine to you. Thanks

Why is my promise not resolving correctly?

exports.addUser = async(req, res) => {
const {
username,
email,
password
} = req.body;
//hash password
const password_hash = await hashPassword(password);
//check whitelist
this.checkWhitelist(email).then(function(response) {
if (response) {
console.log("RESOLVED TRUE")
//POST user to Airtable
new Promise(function(resolve, reject) {
return usersTable.create({
email,
username,
password_hash,
"email_verified": "false"
},
function(err) {
if (err) {
resolve(false);
console.error(err);
res.send({
"Success": false,
"responseCode": 502,
})
}
resolve(true);
res.send({
"Success": true,
"responseCode": 200,
});
}
).then(function(response) {
if (response) {
const EMAIL_SECRET = "xxxxxxxxxxx";
jwt.sign({
'username': username,
},
EMAIL_SECRET, {
expiresIn: '1d',
},
(err, emailToken) => {
const url = `http://localhost:3000/confirmation/${emailToken}`;
transporter.sendMail({
to: args.email,
subject: 'Confirm Email',
html: `Please click this email to confirm your email: ${url}`,
});
}
)
}
})
})
} else {
console.log('RESOLVED FALSE')
res.send({
"Success": false,
"responseCode": 403
})
}
})
}
For some reason, the promise I created at usersTable.create is not resolving correctly. When I call .then() after, I get the error: UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'then' of undefined.
For context, this is the user registration flow for a webapp. First, the pass is hashed, then the email is check against a whitelist (so far this logic is working). Now I just need to verify the email, but can't get the .then() to call correctly.
What's going on?
In your first then where you return createTable you'll need to return new Promise so it can be chained to the next then.
If createTable returns a promise, you can simply write return createTable and get rid of the new Promise that wraps it.
Since you are using async-await earlier in the code, I'd recommend you moving to that completely as it makes the code much easier to read.
I took a stab at that,
exports.addUser = async(req, res) => {
const {
username,
email,
password
} = req.body;
//hash password
const password_hash = await hashPassword(password);
//check whitelist
try {
const whitelist = await this.checkWhiteList(email)
if (whitelist) {
await usersTable.create() // shortened for readability sake.
const EMAIL_SECRET = 'xxxxxxxxxxx';
jwt.sign(
{
'username': username,
},
EMAIL_SECRET,
{
expiresIn: '1d',
},
(err, emailToken) => {
const url = `http://localhost:3000/confirmation/${emailToken}`;
transporter.sendMail({
to: args.email,
subject: 'Confirm Email',
html: `Please click this email to confirm your email: ${url}`,
});
}
);
}
res.send({
'Success': true,
'responseCode': 200,
});
} catch (error) {
res.send({
'Success': false,
'responseCode': 403,
});
}
}

Object is returned the same although I'm mutating it

The upcoming code snippet is removing the password attribute from the user JSON object and return it in response. what is happening is that the password attribute is still returning!
const signin = (req, res, next) => {
let requestBody = req.body;
userModel.findUserByEmail(requestBody.email).then(user => {
bcrypt.compare(requestBody.password, user.password, (error, result) => {
if (!result) {
return res.status(500).json({
status: false,
message: 'Auth Failed!',
error
});
}
if (error) {
return res.status(500).json({
error
});
}
let token = jwt.sign({
email: user.email,
userId: user._id
},
process.env.JWT_KEY,
{
expiresIn: "2h"
});
// remonve password key
delete user.password
res.status(200).json({
status: true,
message: 'Authenticated!',
data: {
token,
user
}
});
});
}).catch(error => {
return res.status(500).json({
status: false,
message: 'Auth Failed!',
error
});
});
}
not sure the problem is related to async compilation or not
You could create a new object without the password and use that in your response:
const { password, ...restOfUser } = user
res.status(200).json({
status: true,
message: 'Authenticated!',
data: {
token
user: restOfUser
}
})

Strange async behavior in Node with Mongoose and promises

So I'm using Node.js + Mongoose + Lie as a promise library. The code is as follows:
var User = require('../models/user'),
Promise = require('lie'),
jwt = require('jsonwebtoken'),
config = require('../../config');
module.exports = function(express) {
var router = express.Router();
router.post('/', function(req, res) {
if (!req.body.username) return res.json({ success: false, reason: 'Username not supplied.' });
if (!req.body.password) return res.json({ success: false, reason: 'Password not supplied.' });
var findUser = new Promise(function(resolve, reject) {
User.findOne({ username: req.body.username }, function(err, user) {
if (err) reject (err);
if (!user) reject({ success: false, reason: 'User not found or password is incorrect.' });
if (!user.validPassword(req.body.password)) reject({ success: false, reason: 'User not found or password is incorrect.' });
resolve(user);
});
});
var sendToken = function(user) {
var token = jwt.sign({ username: user.username }, config.secret, { expiresIn: 2 * 60 * 60 });
res.json({ success: true, token: token });
};
findUser.then(function(value) {
sendToken(value);
}).catch(function(reason) {
res.send(reason);
});
return router;
};
So basically it's an auth route, which sends a signed jwt if everything is good. The strange behavior is that if I send a wrong username, the server throws an error saying
TypeError: Cannot read property 'validPassword' of null
So, it says that the user === null when it reaches the password validity check, HOWEVER, before this validity check, there is a check that a user is found, mainly
if (!user) reject({ success: false, reason: 'User not found or password is incorrect.' });
And here the server already knows that user === null, hence it should reject this promise with success: false, and this piece of code should be thrown into catch part, but it doesn't happen.
Any ideas, please?
I should add that if I change that if (!user.validPassword... part to else if instead of if, it works like it should. However I cannot understand why any code is still executed after the promise gets rejected.
EDIT
While learning node, I mainly used the MEAN Machine book, which is a great help, and there they use this syntax:
if (!user) {
//
} else if (user) {
//
}
But I guess it should work my way as well.
You are not stopping execution of your function on reject, so if findOne results in empty result validPassword is still get called.
Probably you need to add return:
User.findOne({ username: req.body.username }, function(err, user) {
if (err)
return reject (err);
if (!user)
return reject({ success: false, reason: 'User not found or password is incorrect.' });
if (!user.validPassword(req.body.password))
return reject({ success: false, reason: 'User not found or password is incorrect.' });
resolve(user);
});
Or use else if:
User.findOne({ username: req.body.username }, function(err, user) {
if (err) {
reject (err);
} else if (!user) {
reject({ success: false, reason: 'User not found or password is incorrect.' });
} else if (!user.validPassword(req.body.password))
reject({ success: false, reason: 'User not found or password is incorrect.' });
} else {
resolve(user);
}
});

Categories

Resources