How can I make sure that this function handles errors properly? - javascript

I have a function that checks user input in an express application. I don't want to use any library to validate those inputs so I declared an array where errors are pushed into.
I have embedded the middleware function as a static method in a class...
static postAdchecker(req, res, next) {
let { state, price, manufacturer, model, bodytype } = req.body;
console.log('req', req.body);
const errors = [];
// If state is empty or undefined throw this error
if (!state) {
console.log('state', state);
const error = {
message: 'Please specify the state of the car'
};
errors.push(error);
}
// If state is supplied, convert it to lowercase, trim and check if value is new/used
if (state.toLowerCase().trim() !== 'new' && state.toLowerCase().trim() !== 'used') {
const error = {
message: 'State can only be new or used'
};
errors.push(error);
}
// Same goes for the others.
if (!price) {
const error = {
message: 'You will need to specify a sale price'
};
errors.push(error);
}
if (!manufacturer) {
const error = {
message: 'Specify a manufacturer'
};
errors.push(error);
}
if (!model) {
const error = {
message: 'Specify a model'
};
errors.push(error);
}
if (!bodytype) {
const error = {
message: 'You will need to specify a bodytype'
};
errors.push(error);
}
return res.status(400).json({
status: 400,
errors: {
body: errors.map(err => err.message)
}
});
console.log('errors', errors);
req.body.state = state.toLowerCase().trim();
req.body.price = price.toLowerCase().trim();
req.body.manufacturer = manufacturer.toLowerCase().trim();
req.body.model = model.toLowerCase().trim();
req.body.bodytype = bodytype.toLowerCase().trim();
// req.authData;
return next();
}
How can I achieve the following?
Convert the values in the input field to lowercase and trim when supplied.
When there are errors, return all the errors.
When there are no errors, transfer operation to the next function instead of returning an empty array.

You are just missing one condition:
if(errors.length) { // <<<
return res.status(400).json({
status: 400,
errors: {
body: errors.map(err => err.message)
}
});
}

Related

array returned in response object is empty

I have started using typescript. As part of project, I am using multer and csvtojson npm modules to upload a csv file to application and then return the data from csv file into Mongo Database.
Able to push elements to arrays members and errors i.e., confirmed by console logging the length of array, but array is returning empty in response object
//controller to import members
exports.importMembers = async (req: Request, res: Response) => {
try {
uploadFile(req, res); //Fileupload method is return in below
}
catch (error: any) {
if(error) {
return res.status(400).json({
status_code: 400,
Error: error
});
}
}
};
//method to uplaod data
const uploadFile = async (req: Request, res: Response) => {
try {
//arrays to store errors occured and members saved from file
var errors:{}[] = [], members:{}[] = [];
let filePath : string;
// multer method
upload(req, res, (err: any) => {
if(!(req.file)) {
return res.status(400).json({
status_code: 400,
Message: "Please select file to be imported!",
})
}
else if (err) {
return res.status(400).json({
status_code: 400,
Info: `Error occurred while uploading file: ${err}`,
});
}
else {
filePath = path.join("upload", req.file?.originalname);
csvtojson().fromFile(filePath).then((source: any) => {
// Fetching all data from each row and return to member object
for (var i = 0; i < source.length; i++)
{
var member = {
memberEnrollmentID: source[i]["Member Enrollment ID"],
name: source[i]["Name"],
dateOfBirth: source[i]["Date Of Birth"],
IDproofNumber: source[i]["ID Proof Number"],
email: source[i]["Email"],
phoneNumber: source[i]["Phone Number"],
address: source[i]["Address"]
};
const newMember = new Members(member); //saving member to collection
newMember.save().then((result) => {
console.log(members.length); // returning array length
members.push(result); // push saved members
}).catch((error) => {
console.log(errors.length); // returning array length
errors.push(error); // push errors occured while saving members
})
}
console.log(members.length); // returning 0
console.log(errors.length); // returning 0
return res.status(200).json({
status_code: 200,
Members_Imported : members, // returning empty array
Members_Rejected : errors // returning empty array
});
});
}
});
} catch (error: any) {
throw error; // throw an error to be handled by the calling function
}
};
How could i return the members and errors array with elements stored in a express response object?

Error in updating profile with image using mongoose and cloudinary

updateProfile: async function(req, res) {
try {
const update = req.body;
const id = req.params.id;
if (!req.files || Object.keys(req.files).length === 0) {
return res.status(400).send('No files were uploaded.');
}
const image = req.files.profileImage;
const cloudFile = await upload(image.tempFilePath);
const profileImage = cloudFile.url
console.log('Loging cloudfile', profileImage)
await User.updateOne(id, { update }, { profileImage }, { new: true },
function(err, doc) {
if (err) {
console.log(err)
}
if (doc) {
return res.status(200).send({ sucess: true, msg: 'Profile updated successful' })
}
});
} catch (error) {
res.status(500).json({ msg: error.message });
}
}
But I'm getting an error of "Callback must be a function, got [object Object]"
I have tried to $set: update and $set: profileImage but still not working.
So the image successful upload into the cloudinary but the update for mongoose is not working.
Upon brief research into the issue, I think you are feeding the arguments in wrong. Objects can be confusing but not to worry.
Your code is:
await User.updateOne(id, { update }, { profileImage }, { new: true }
However, I believe it should be something more like:
await User.updateOne({id: id}, { profileImagine: profileImage, new: true },
The API reference annotates use of the function as:
const filter = { name: 'John Doe' };
const update = { age: 30 };
const oldDocument = await User.updateOne(filter, update);
oldDocument.n; // Number of documents matched
oldDocument.nModified; // Number of documents modified

User object is returning undefined when trying to assign one of its attributes to a new variable

I'm trying out this code to create a simple order and then when trying to assign the user.shoppingCart Array to a new variable ("products") it says the user is undefined but for example the address is working just fine and then trying to console.log the user.address and user.shoppingCart it actually prints the correct values. Any ideas?
exports.createOrder = async (req, res) => {
try {
const user = await User.findById(req.user.id);
if (user.shoppingCart.length < 1) {
return res.status(400).json({
status: 'fail',
message: 'Please first add a product to your shopping cart.',
});
}
const address = req.body.address || user.address;
const products = user.shoppingCart;
const total = await getTotal();
const { paymentMethod } = req.body;
const order = await Order.create({
address,
user: user._id,
products,
total,
paymentMethod,
});
res.status(201).json({
status: 'success',
data: {
order,
},
});
} catch (err) {
res.status(500).json({
status: 'fail',
message: err.message,
});
}
};

How to add custom validator function in Joi?

I have Joi schema and want to add a custom validator for validating data which isn't possible with default Joi validators.
Currently, I'm using the version 16.1.7 of Joi
const method = (value, helpers) => {
// for example if the username value is (something) then it will throw an error with flowing message but it throws an error inside (value) object without error message. It should throw error inside the (error) object with a proper error message
if (value === "something") {
return new Error("something is not allowed as username");
}
// Return the value unchanged
return value;
};
const createProfileSchema = Joi.object().keys({
username: Joi.string()
.required()
.trim()
.empty()
.min(5)
.max(20)
.lowercase()
.custom(method, "custom validation")
});
const { error,value } = createProfileSchema.validate({ username: "something" });
console.log(value); // returns {username: Error}
console.log(error); // returns undefined
But I couldn't implement it the right way. I read Joi documents but it seems a little bit confusing to me. Can anyone help me to figure it out?
const Joi = require('#hapi/joi');
Joi.object({
password: Joi
.string()
.custom((value, helper) => {
if (value.length < 8) {
return helper.message("Password must be at least 8 characters long")
} else {
return true
}
})
}).validate({
password: '1234'
});
Your custom method must be like this:
const method = (value, helpers) => {
// for example if the username value is (something) then it will throw an error with flowing message but it throws an error inside (value) object without error message. It should throw error inside the (error) object with a proper error message
if (value === "something") {
return helpers.error("any.invalid");
}
// Return the value unchanged
return value;
};
Docs:
https://github.com/hapijs/joi/blob/master/API.md#anycustommethod-description
Output for value :
{ username: 'something' }
Output for error:
[Error [ValidationError]: "username" contains an invalid value] {
_original: { username: 'something' },
details: [
{
message: '"username" contains an invalid value',
path: [Array],
type: 'any.invalid',
context: [Object]
}
]
}
This is how I validated my code, have a look at it and try to format yours
const busInput = (req) => {
const schema = Joi.object().keys({
routId: Joi.number().integer().required().min(1)
.max(150),
bus_plate: Joi.string().required().min(5),
currentLocation: Joi.string().required().custom((value, helper) => {
const coordinates = req.body.currentLocation.split(',');
const lat = coordinates[0].trim();
const long = coordinates[1].trim();
const valRegex = /-?\d/;
if (!valRegex.test(lat)) {
return helper.message('Laltitude must be numbers');
}
if (!valRegex.test(long)) {
return helper.message('Longitude must be numbers');
}
}),
bus_status: Joi.string().required().valid('active', 'inactive'),
});
return schema.validate(req.body);
};

How to fix "Error: Can't set headers after they are sent" in Express

I have recently been developing a MERN application and I have recently came into the trouble that express is saying that I am setting headers after they are sent.
I am using mongo db and trying to update a user profile.
I have tried to comment out my res.send points to find the issue but I have failed to do so.
Here is my post method for updating the user profile:
app.post("/api/account/update", (req, res) => {
const { body } = req;
// Validating and Checking Email
if (body.email) {
var email = body.email;
email = email.toLowerCase();
email = email.trim();
body.email = email;
User.find(
{
email: body.email
},
(err, previousUsers) => {
if (previousUsers.length > 0) {
return res.send({
success: false,
message:
"Error: There is already another account with that email address"
});
} else {
}
}
);
}
// Validating Names Function
function checkName(name) {
var alphaExp = /^[a-zA-Z]+$/;
if (!name.match(alphaExp)) {
return res.send({
success: false,
message: "Error: Names cannot contain special characters or numbers"
});
}
}
checkName(body.firstName);
checkName(body.lastName);
// Making sure that all fields cannot be empty
if (!body.email && !body.firstName && !body.lastName) {
return res.send({
success: false,
message: "Error: You cannot submit nothing"
});
}
// Getting User ID from the current session
UserSession.findById(body.tokenID, function(err, userData) {
// Finding User ID using the current users session token
if (userData.isDeleted) {
return res.send({
success: false,
message:
"Error: Session token is no longer valid, please login to recieve a new one"
});
}
// Deleting the token ID from the body object as user table entry doesnt store tokens
delete body.tokenID;
// Finding the user profile and updating fields that are present
User.findByIdAndUpdate(userData.userId, body, function(err, userInfo) {
if (!err) {
return res.send({
success: true,
message: "Success: User was updated successfully"
});
}
});
});
});
This is the call that I am doing to the backend of the site:
onUpdateProfile: function(fieldsObj) {
return new Promise(function(resolve, reject) {
// Get Session Token
const obj = getFromStorage("the_main_app");
// Defining what fields are getting updated
fieldsObj.tokenID = obj.token;
// Post request to backend
fetch("/api/account/update", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(fieldsObj)
})
.then(res => {
console.log("Verify Token - Res");
return res.json();
})
.then(json => {
console.log("Verify Token JSON", json);
if (json.success) {
window.location.href = `/manage-account?success=${json.success}`;
} else {
window.location.href = `/manage-account?success=${json.success}`;
}
});
});
}
Here is my error message that I am getting:
Error: Can't set headers after they are sent.
at validateHeader (_http_outgoing.js:491:11)
at ServerResponse.setHeader (_http_outgoing.js:498:3)
at ServerResponse.header (C:\Users\kieran.corkin\Desktop\Projects\Mern Template Final\mern-cra-and-server\server\node_modules\express\lib\response.js:767:10)
at ServerResponse.send (C:\Users\kieran.corkin\Desktop\Projects\Mern Template Final\mern-cra-and-server\server\node_modules\express\lib\response.js:170:12)
at ServerResponse.json (C:\Users\kieran.corkin\Desktop\Projects\Mern Template Final\mern-cra-and-server\server\node_modules\express\lib\response.js:267:15)
at ServerResponse.send (C:\Users\kieran.corkin\Desktop\Projects\Mern Template Final\mern-cra-and-server\server\node_modules\express\lib\response.js:158:21)
at C:\Users\kieran.corkin\Desktop\Projects\Mern Template Final\mern-cra-and-server\server\routes\api\account.js:270:22
at C:\Users\kieran.corkin\Desktop\Projects\Mern Template Final\mern-cra-and-server\server\node_modules\mongoose\lib\model.js:4641:16
at process.nextTick (C:\Users\kieran.corkin\Desktop\Projects\Mern Template Final\mern-cra-and-server\server\node_modules\mongoose\lib\query.js:2624:28)
at _combinedTickCallback (internal/process/next_tick.js:131:7)
at process._tickCallback (internal/process/next_tick.js:180:9)
[nodemon] app crashed - waiting for file changes before starting...
Can anyone help me with this?
EDIT
I have changed my code, this seems to now work however I feel like its a little messy when put together. Any refactoring tips?
Code:
app.post("/api/account/update", (req, res) => {
// Preform checks on data that is passed through
const { body } = req;
var messages = {
ExistedUser:
"Error: There is already another account with that email address",
NameFormat: "Error: Names cannot contain special characters or numbers",
BlankInputs: "Error: You cannot submit nothing",
accountLoggedOut:
"Error: Session token is no longer valid, please login to recieve a new one",
successfullyUpdated: "Success: User was updated successfully"
};
var usersFound;
if (body.email) {
var email = body.email;
email = email.toLowerCase();
email = email.trim();
body.email = email;
User.find(
{
email: body.email
},
(err, UserCount) => {
usersFound = UserCount;
}
);
}
function capitalize(text) {
return text.replace(/\b\w/g, function(m) {
return m.toUpperCase();
});
}
if (body.firstName) {
body.firstName = capitalize(body.firstName);
}
if (body.lastName) {
body.lastName = capitalize(body.lastName);
}
//Making sure that all fields cannot be empty
if (!body.email && !body.firstName && !body.lastName) {
return res.send({
success: false,
message: messages.BlankInputs
});
}
// Getting User ID from the current session
UserSession.findById(body.tokenID, function(err, userData) {
// Finding User ID using the current users session token
if (userData.isDeleted) {
return res.end({
success: false,
message: messages.accountLoggedOut
});
}
if (userData) {
// Deleting the token ID from the body object as user table entry doesnt store tokens
delete body.tokenID;
// Finding the user profile and updating fields that are present
User.findByIdAndUpdate(userData.userId, body, function(err, userInfo) {
if (userInfo) {
if (!usersFound.length > 0) {
return res.send({
success: true,
message: messages.successfullyUpdated
});
} else {
return res.send({
success: false,
message: messages.ExistedUser
});
}
}
});
}
});
});
You're calling res.send() twice. res.send() ends the process. You ought to refactor such that you call res.write() and only call res.send() when you're done.
This StackOverflow link describes the difference in more detail. What is the difference between res.send and res.write in express?
I believe this is happening, as you're trying to send a response after the first / initial response has already been sent to the browser. For example:
checkName(body.firstName);
checkName(body.lastName);
Running this function twice is going to try and yield 2 different "response" messages.
The product of a single route, should ultimately be a single response.
Thanks for all your help on this issue.
Here is my final code that allowed it to work.
I have also tried to "refactor" it too. Let me know if you'd do something else.
app.post("/api/account/update", (req, res) => {
const { body } = req;
console.log(body, "Logged body");
// Defining objects to be used at the end of request
var updateUserInfo = {
userInfo: {},
sessionToken: body.tokenID
};
var hasErrors = {
errors: {}
};
// Checking that there is at least one value to update
if (!body.email && !body.firstName && !body.lastName) {
var blankError = {
success: false,
message: "Error: You cannot change your details to nothing"
};
hasErrors.errors = { ...hasErrors.errors, ...blankError };
} else {
console.log("Normal Body", body);
clean(body);
console.log("Cleaned Body", body);
updateUserInfo.userInfo = body;
delete updateUserInfo.userInfo.tokenID;
}
// Function to check if object is empty
function isEmpty(obj) {
if (Object.keys(obj).length === 0) {
return true;
} else {
return false;
}
}
// Function to remove objects from body if blank
function clean(obj) {
for (var propName in obj) {
if (obj[propName] === "" || obj[propName] === null) {
delete obj[propName];
}
}
}
// Checking and Formatting Names Given
function capitalize(text) {
return text.replace(/\b\w/g, function(m) {
return m.toUpperCase();
});
}
if (body.firstName) {
body.firstName = capitalize(body.firstName);
}
if (body.lastName) {
body.lastName = capitalize(body.lastName);
}
// Checking and formatting email
if (body.email) {
body.email = body.email.toLowerCase();
body.email = body.email.trim();
// Checking for email in database
User.find({ email: body.email }, (err, EmailsFound) => {
if (EmailsFound.length > 0) {
var EmailsFoundErr = {
success: false,
message: "There is already an account with that email address"
};
hasErrors.errors = { ...hasErrors.errors, ...EmailsFoundErr };
}
});
}
// Getting User Session Token
UserSession.findById(updateUserInfo.sessionToken, function(err, userData) {
// Finding User ID using the current users session token
if (userData.isDeleted) {
var userDeletedError = {
success: false,
message:
"Your account is currently logged out, you must login to change account details"
};
hasErrors.errors = { ...hasErrors.errors, ...userDeletedError };
} else {
// Finding the user profile and updating fields that are present
User.findByIdAndUpdate(
userData.userId,
updateUserInfo.userInfo,
function(err, userInfo) {
// userInfo varable contains user db entry
if (err) {
var updateUserError = {
success: false,
message: "Error: Server Error"
};
hasErrors.errors = {
...hasErrors.errors,
...updateUserError
};
}
if (isEmpty(hasErrors.errors)) {
res.send({
success: true,
message: "Success: You have updated your profile!"
});
} else {
res.send({
success: false,
message: hasErrors.errors
});
}
}
);
}
});
});

Categories

Resources