Separating Mongoose code from Express Router - javascript

So basically, I'm trying to separate my code that handles data (mongoose) from my express Router code, since I might want to use it elsewhere too.
The first thing I did was, I got rid of the res.json() calls, since I don't want the code to only work returning a http response. I want it to return data, so I can then return that data from my router as a http response, but still use it as regular data elsewhere.
Here is a function I wrote to get data from mongoose.
module.exports.user_login = data => {
console.log(data);
ModelUser.findOne({email: data.email}).then(user => {
if(!user){
console.log({email: 'E-mail address not found'});
return {
status: response_code.HTTP_404,
response: {email: 'E-mail address not found'}
}
}
bcrypt.compare(data.password, user.password).then(isMatch => {
if(!isMatch){
console.log({password: 'Invalid password'});
return {
status: response_code.HTTP_400,
response: {password: 'Invalid password'}
}
}
const payload = {
id: user.id,
email: user.email
};
jwt.sign(
payload,
config.PASSPORT_SECRET,
{
expiresIn: "1h"
},
(err, token) => {
console.log({
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
});
return {
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
}
}
);
});
});
};
When this code gets executed in my route like so:
router.post("/login", (req, res) => {
const { errors, isValid } = validateLogin(req.body);
if(!isValid) return res.status(400).json(errors);
console.log("ret", dm_user.user_login(req.body));
});
The log says the return value of user_login() is undefined, even though right before the return statement in user_login() I am logging the exact same values and they are getting logged.
Before I changed it to a log, I tried to store the return value in a variable, but obviously that remained undefined as well, and I got the error: 'Cannot read propery 'status' of undefined' when trying to use the value.
I am definitely missing something..

Well you have an small callback hell here. It might be a good idea to go with async / await and splitting up your code into smaller chunks instead of putting everyhing in 1 file.
I rewrote your user_login function:
const { generateToken } = require("./token.js");
module.exports.user_login = async data => {
let user = await ModelUser.findOne({ email: data.email });
if (!user) {
console.log({ email: "E-mail address not found" });
return {
status: response_code.HTTP_404,
response: { email: "E-mail address not found" }
};
}
let isMatch = await bcrypt.compare(data.password, user.password);
if (!isMatch) {
console.log({ password: "Invalid password" });
return {
status: response_code.HTTP_400,
response: { password: "Invalid password" }
};
}
const payload = {
id: user.id,
email: user.email
};
let response = await generateToken(
payload,
config.PASSPORT_SECRET,
response_code
);
return response;
};
I have moved your token signing method into another file and promisfied it:
module.exports.generateToken = (payload, secret, response_code) => {
return new Promise((res, rej) => {
jwt.sign(
payload,
secret,
{
expiresIn: "1h"
},
(err, token) => {
if (err) {
rej(err);
}
res({
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
});
}
);
});
};
Now you need to change your router function into an async:
router.post("/login", async (req, res) => {
const { errors, isValid } = validateLogin(req.body);
if(!isValid) return res.status(400).json(errors);
let result = await dm_user.user_login(req.body);
console.log(result);
});
In addition: You get undefined because you return your value to an callback function
I also would seperate your routes from your controllers instead of writing your code inside an anonymous function

Please notice that whenever you are trying to return any value you are always present in the callback function and that is definitely not going to return any value to its intended place.
There are a couple of things you can improve about your code :
1.Donot use jwt inside your code where you are making database calls, instead move it where your routes are defined or make a separate file.
2.If you are intending to re-use the code, I would suggest you either use async-await as shown in the answer above by Ifaruki or you can use something like async.js. But the above shown approach is better.
Also always use 'error' field when you are making db calls like this:
ModelUser.findOne({email: data.email}).then((error,user) => {

Related

Sequelize update via PATCH, how to process each possible result

I'm creating a rest api for CRUD operations using Sequelize and MySql. I'm using a controller to run an update on a PATCH request to update fields of a product. It technically works, but I feel like there is a more elegant way to handle this.
Sequelize's update method will return an array of objects depending on the results. Array[0] is the number of rows affected by the update (should just be one in my case, as I'm updating by id). Array[1] will return an object with details about the update as well as all the old values and new values. Here's how I'm handling that currently:
//products.controller.js
//Update a single product using id (PUT/PATCH)
const patch = (req, res) => {
const id = req.params.id;
Product.update(req.body, { where: { id }, individualHooks: true })
.then((rowsAffected) => {
//Item not found
if (Object.entries(rowsAffected[1]).length === 0) {
res.status(404).send({
success: false,
status: 404, //Not found
message: `Product with id ${id} not found. Update failed.`,
});
return;
}
//if rowsAffected[0] === 1 then success
if (rowsAffected[0] === 1) { //row changed
res.status(200).send({
success: true,
status: 200,
message: `Product updated.`,
id: id,
payload: req.body,
});
} else {
// if rowsAffected[0] !== 1 then it failed.
res.status(200).send({
success: false,
status: 200, //Not Modified
message: `No fields have changed. Product not updated.`,
});
}
})
.catch((err) => {
res.status(500).send({
success: false,
status: 500,
message:
err.message || "Something went wrong while updating the product.",
});
});
}
As you can see, first I'm checking to see if the the update function returns the product details (meaning it successfully found it in the database). If not then sending 404. Then I check the affected rows. If 1 then success, if 0 then nothing changed. Finally I'm catching any server errors.
I feel like there is a better way rather than having to break down the update function's return (like Object.entries(rowsAffected[1]).length === 0)
This is ok if this is the only way you can check the effects of the update. What I can suggest is putting an abstraction above it.
First thing that checking (rowsAffected[0] === 1) does not make much sense, since the update is idempotent and you end up with the same resource state no matter what the actual values are. If you insist, then I would not pair success: false with a 200 ok status, because failure is failure and it requires an error message and 4xx or 5xx status. So either delete it or convert it into a proper error. Hard to find such a status code, but maybe using 409 conflict is ok in these cases https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 though I would just remove this part of the code. I keep it for the sake of the example.
As of the success and status properties in the body, they don't make much sense either, because they travel in the header, and it is evident from the HTTP standard that 2xx means success, 4xx and 5xx means error. So I would remove those too.
If you don't want to support detailed error codes and exception types and parameters, then just send the error messages and the body can be even a string instead of an object.
Sending the err.message to the consumers is a bad idea by unexpected errors. You don't know what you send out. You need to log them and send something general instead. Communicating errors is always a higher abstraction level stuff, many times. As of the Product with id ${id} not found. Update failed. here adding the id is not necessary, because the request contains it.
So atm. the code looks like this:
const patch = (req, res) => {
const id = req.params.id;
Product.update(req.body, { where: { id }, individualHooks: true })
.then((rowsAffected) => {
if (Object.entries(rowsAffected[1]).length === 0) {
res.status(404).send({message: `Product not found. Update failed.`});
return;
}
//if rowsAffected[0] === 1 then success
if (rowsAffected[0] === 1) { //row changed
res.status(200).send({
message: `Product updated.`,
id: id,
payload: req.body,
});
} else {
res.status(409).send({message: "No fields have changed. Product not updated."});
}
})
.catch((err) => {
res.status(500).send({message: "Something went wrong while updating the product."});
});
}
We can go further by mapping status codes to status messages and extracting the possibly repeating parts of the story into separate functions.
const patch = (req, res) => {
const id = req.params.id;
const statusMessages = {
200: "Product updated."
404: "Product not found. Update failed."
409: "No fields have changed. Product not updated.",
500: "Something went wrong while updating the product."
};
Product.update(req.body, { where: { id }, individualHooks: true })
.then(updateStatusVerification)
.then(successHandler(res, statusMessages, () => {
return {
id: id,
payload: req.body,
};
}))
.catch(apiErrorHandler(res, statusMessages));
}
function successHandler(res, statusMessages, callback){
return function (){
let body = callback();
body.message = statusMessages[200];
res.status(200).send(body);
};
}
function apiErrorHandler(res, statusMessages){
return function (err){
let statusCode = 500;
if (err instanceof NotFoundError)
statusCode = 404;
else if (err instanceof NotUpdatedError)
statusCode = 409;
res.status(statusCode).send({
message: statusMessages[statusCode]
});
};
}
function updateStatusVerification(rowsAffected){
return new Promise((resolve, reject) => {
if (Object.entries(rowsAffected[1]).length === 0)
reject(new NotFoundError);
else if (rowsAffected[0] !== 1)
reject(new NotUpdatedError);
else
resolve();
});
}
class ApiError extends Error {}
class NotFoundError extends ApiError {}
class NotUpdatedError extends ApiError {}
We can move the status messages to the documentation. So you will end up with something like this and some utility functions:
const patch = (req, res) => {
const id = req.params.id;
statusMessages = docs.product.update.statusMessages;
Product.update(req.body, { where: { id }, individualHooks: true })
.then(updateStatusVerification)
.then(successHandler(res, statusMessages, () => {
return {
id: id,
payload: req.body,
};
}))
.catch(apiErrorHandler(res, statusMessages));
}
We can go even further if this is a frequent pattern:
const patch = (req, res) => {
const id = req.params.id;
handleUpdate(
Product.update(req.body, { where: { id }, individualHooks: true }),
() => {id: id, payload: req.body},
docs.product.update.statusMessages
);
}
function handleUpdate(dbUpdatePromise, successCallback, statusMessages){
dbUpdatePromise.then(updateStatusVerification)
.then(successHandler(res, statusMessages, successCallback))
.catch(apiErrorHandler(res, statusMessages));
}
So it can be as abstract as you like, it really depends on your needs and what the current usage allows. You can decide how many and what kind of layers you need based on actual use cases and repetitions.

Dealing with Promises and error handling in Next.js

I am new to React and Next.js I am trying to send an email via sendGrid from a contact form. I have combined a couple of tutorials to get what I want but I am clearly not understanding something.
Using Next.js I have a contact form /pages/contact.js onSubmit calls /pages/api/sendMail which imports a function sendMailToMe() from utils/sendMailToMe.js
The code works and sends the email but I cannot seem to pass the result from sendMailToMe() back to sendMail.js
/utils/sendMailToMe.js
const sendMailToMe = async (
fullName,
formMessage,
email
) => {
const mail = require('#sendgrid/mail');
mail.setApiKey(SENDGRID_API_KEY);
const msg = {
to: 'mike#mydomain.com',
from: 'mike#mydomain.com',
templateId: 'd-3481ff06ea924128baa7c16a5a7f4840',
dynamicTemplateData: {
subject: 'Testing Templates',
fullName: fullName,
message: formMessage,
},
};
mail.send(msg)
.then((response) => {
console.log('in response')
console.log(response[0].headers)
return response[0].statusCode
})
.catch((error) => {
console.log("there was an error")
console.error(error)
return 'test'+error
})
//return response;
}
export { sendMailToMe };
This is imported and called as follows to pages/api/sendMail.js
import { sendMailToMe } from "../../utils/sendMailToMe";
export default async function handler(req, res) {
if (req.method === "POST") {
const { email, fullName, message,test } = req.body;
if (
typeof (email || fullName || test || message) === "undefined"
) {
console.log(" ************* Invalid Data received ************ ");
return res
.status(400)
.send({ error: "bad request, missing required data!" });
} else {
// Data received as expected
console.log('Calling sendMailToMe')
const sendGridResult = await sendMailToMe(
fullName,
message,
email
)
.then((response)=>{console.log(response)}) //res.status(200).send({test: 'test'})})
.catch((err) =>{ console.log(err)})//res.status(400).send({error:"Error in Sendgrid", errMsg:err})})
// API returns here regardless of outcome
res.status(200).send({test: 'returning here on error or success'})
}
}else{
res.status(400).send({ error: "Must use POST method" });
}
//res.status(400).send({ error: "bad request somehow" });
}
I am trying to get the result of mail.send() back to the api so I can return the proper response. Right now sendMail.js returns 200 even if mail.send() fails. The console logs the response or error in sendMailToMe() but I can't get the response or error back to sendmail.js. Any pointers in the right direction appreciated.

toObject is not a function error while converting mongoose object

I have a route '/login' which has controller to verify user is valid. For now, i am just verifying password entered from route params equals to password i stored in my DB.
I have few methods to get job done for me.
findUser() - verifies user entered mandatory fields or not and resolves Mongoose object when data is found
ValidatePassword() - It is chained to findUser() and converts mongoose object to JS object
In validatePassword method, i want to delete unwanted fields like password before sending data to client. As a result i am converting mongoose object to javascript object and performing delete operation.
Problem: Whenever i am converting in to JS object, i am getting 'toObject() not defined' error.
Attaching snippets for above controller methods.
Please suggest any changes!
let findUser = (req, res) => {
return new Promise((resolve, reject) => {
if (req.body.email) {
userModel.find({ email: req.body.email }, (error, userDetails) => {
if (error) {
let response = apiResponse.generate(
true,
"Unable to reach out to server",
500,
null
);
reject(response);
} else if (checkLib.isEmpty(userDetails)) {
let response = apiResponse.generate(
true,
"Unable to reach out to server",
500,
null
);
reject(response);
} else {
resolve(userDetails);
}
});
} else {
let response = apiResponse.generate(
true,
"Please provide emailID and password",
404,
null
);
reject(response);
}
});
};
This method retrieves userDetails which i am chaining in validatePassword()
let validatePassword = (retrievedUserDetails) => {
return new Promise((resolve, reject) => {
console.log("passw:" + retrievedUserDetails);
if (req.body.password) {
console.log(retrievedUserDetails.password);
console.log(req.body.password);
if (retrievedUserDetails[0].password != req.body.password) {
let response = apiResponse.generate(
true,
"Password or email is invalid",
404,
null
);
reject(response);
} else {
let retrievedUserDetailsObj = retrievedUserDetails.toObject();
delete retrievedUserDetailsObj.password;
let response = apiResponse.generate(
false,
"Signed in successfully",
200,
retrievedUserDetails
);
resolve(response);
}
} else {
let response = apiResponse.generate(
true,
"Please provide password",
404,
null
);
reject(response);
}
});
}
Chaining:
findUser(req, res)
.then(validatePassword)
.then((resolve) => {
let response = apiResponse.generate(
false,
"user is signed in successfully",
200,
resolve
);
res.send(response);
})
.catch((err) => {
console.log(err);
res.send(err);
});
};
It seems you're using the .find method of the model and not the .findOne method. The first one will always return an array of documents that match the query while the second one will return the first object that matches. What you're basically trying to do is [{something}].toObject() and that is indeed undefined.
Use findOne, instead of find because .toObject works on a single object, not an array.
If you want to run .toObject on the array, just loop the array and run .toObject on single object of the array in the loop.

How to check if there are no more documents to update using findOneAndUpdate

So I am learning CRUD for a school project and I followed a tutorial that was really useful. However, when I completed it I noticed that when there are no more quotes to update, it still updates quotes. How can I change this so that it will stop updating quotes that arent even there?
app.put('/quotes', (req, res) => {
quoteCollection.findOneAndUpdate(
{ name: 'Yoda' },
{
$set: {
name: req.body.name,
quote: req.body.quote
}
},
{upsert: true}
)
.then(result => {
//The if block that i am trying
if (result.deletedCount === 0) {
return res.json('No quote to delete')
}
})
.catch(error => console.error(error))
})
Why are you passing {name: "Yoda}? This route is supposed to only update the quote with "Yoda" as its name? If not, then you need to grab from the request object the quote that should be updated.
I tried to create a different version, based on the assumption that the quote that should be updated will come from the req.body:
app.put("/quotes", async (req, res) => {
//Grab the name/id/identifier for the quote you want to update from the body
const query = req.body.name;
// Try to update the document on the database
try {
const result = await quoteCollection.findOneAndUpdate(
query,
{
name: req.body.name,
quote: req.body.quote,
},
{
upsert: true,
new: true,
}
);
// If it worked, it will return the updated quote
res.status(200).json({
status: 200,
data: {
result,
},
});
} catch (err) {
res.status(400).json({
status: 400,
message: "Something went wrong",
});
}
});

Adonisjs: how to catch exception in lucid model creation?

I'm building RESTful api with adonisjs. I face this problem in jwt login module. Look at the code below:
async login({request, auth, response}) {
let {email, password} = request.all()
try {
if (await auth.attempt(email, password)) {
let user = await User.findBy('email', email)
let token = await auth.generate(user)
user.password = undefined
Object.assign(user, token)
//------------------------------------------
const assignedToken = await Token.create({
user_id: user.id,
token,
})
// -------- i'd like to catch exception here...
return response.json({
success: true,
user
})
}
} catch(e) {
return response.json({
success: false,
message: 'login_failed'
})
}
}
I'd like to catch possible exception while persisting jwt token to database. I am rather new to adonis. I checked their doc but cannot find exact return type. Do they throw an exception? Or just return null/false? I have no idea. Do you have any?
Do they throw an exception?
Yes
An exception will appear if there is a problem during creation. You can create a new try/catch inside you try/catch. Like:
async login({request, auth, response}) {
...
try {
...
try { // New try/catch
const assignedToken = await Token.create({
user_id: user.id,
token,
})
} catch (error) {
// Your exception
return ...
}
return response.json({
success: true,
user
})
}catch (e) {
...
}
}
It's the simplest solution. I would do it this way because there can be different types of errors.

Categories