I add a validator like that:
validate: (req, res, next) => {
req.sanitizeBody("email").normalizeEmail({
all_lowercase: true}).trim();
req.check("password", "Password cannot be empty").notEmpty();
// Collect the results of previous validations.
console.log(req.getValidationResult());
req.getValidationResult().then((error) => {
if (!error.isEmpty()) {
req.skip = true;
res.locals.redirect = "/route1";
next();
} else {
next(); // Call the next middleware function.
}
});
}
and in the routes I have the following:
route.post("/route2", controller.validate, controller.save, controller.redirectView);
the save action allows to save a student in my database (mongodb):
save : (req, res, next) => {
if (req.skip) next() ;
let student = {
email : req.body.email,
password : req.body.password,
zipCode : req.body.zipCode
};
const newStudent = new Student(student);
newStudent
.save() // method
.then((student) => {
res.locals.redirect = "/students";
res.locals.student = student;
next();
})
.catch(error => {
next(error);
});
}
and redirectView to rendrer a view.
redirectView : (req, res, next) => {
const redirectPath = res.locals.redirect;
if(redirectPath) res.redirect(redirectPath);
next();
},
The validation works (I think since in case of error, the redirectView is executed and the save action is skipped.) but I see in the terminal:
Promise { <pending> }
ERROR occurred: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at new NodeError (node:internal/errors:371:5)
at ServerResponse.setHeader (node:_http_outgoing:576:11)
at ServerResponse.header (myapplicationPath\node_modules\express\lib\response.js:767:10)
....
and I have in another file the following code:
exports.pageError = (req, res) => {
let errorCode = httpStatus.NOT_FOUND;
res.status(errorCode);
res.render("error");
};
I cannot usderstand where is my error.
I am creating an express app using mongoose with the intention of connecting this to React for the frontend.
I have listed some CRUD operations for a customer controller below but there are a few things I do not like about this approach.
When using Customer.findById with a valid ObjectID that is not found, it returns null with a 200 response code. I want this to return 404 if no customer was found. I realise I could change the catch response to a 404, but I want to have some generic error handling incase the server goes down during the request or an invalid ObjectId was provided, which brings me to my next item.
If I provide an invalid ObjectId I want to provide some meaningful message, is 500 the right response code?
Error handling: Am I returning errors the correct way? currently errors return a string with the error message. Should I return JSON instead? e.g. res.status(500).json({error: error.message). I am planning on connecting this to react (which I am still learning) and I assume the UI will need to display these messages to the user?
findById is repeated in getCustomerById, updateCustomer, and deleteCustomer. I feel this is bad practice and there must be a more streamlined approach?
I want to have one function that validates if the ObjectId is valid. I am aware that I can do this is the routes using router.params but I'm not sure if checking for a valid id should be in the routes file as it seems like something the controller should be handling? See routes example below from another project I did.
What are the best practices and suggested ways to improve my code, based on the above?
I have read the documentation from mongoose, mozilla, and stackoverflow Q&A but they don't seem to address these issues (at least I could not find it).
I am really after some guidance or validation that what I am doing is correct or wrong.
customer.controller.js
const Customer = require("../models/customer.model");
exports.getCustomers = async (req, res) => {
try {
const customers = await Customer.find();
res.status(200).json(customers);
} catch (error) {
res.status(500).send(error.message);
}
};
exports.getCustomerById = async (req, res) => {
try {
const customer = await Customer.findById(req.params.id);
res.status(200).json(customer);
} catch (error) {
res.status(500).send(error.message);
}
};
exports.addCustomer = async (req, res) => {
try {
const customer = new Customer(req.body);
await customer.save().then(res.status(201).json(customer));
} catch (error) {
res.status(500).send(error.message);
}
};
exports.updateCustomer = async (req, res) => {
try {
const customer = await Customer.findById(req.params.id);
Object.assign(customer, req.body);
customer.save();
res.status(200).json(customer);
} catch (error) {
res.status(500).send(error.message);
}
};
exports.deleteCustomer = async (req, res) => {
try {
const customer = await Customer.findById(req.params.id);
await customer.remove();
res.status(200).json(customer);
} catch (error) {
res.status(500).send(error.message);
}
};
Router.params example
This is a routes file (not related to my current app) and is provided as an example of how I have used router.params in the past.
const express = require("express");
const router = express.Router();
const mongoose = require("mongoose");
const Artist = require("../models/Artist");
const loginRequired = require("../middleware/loginRequired");
const {
getArtists,
addArtist,
getArtistById,
updateArtist,
deleteArtist,
} = require("../controllers/artistController");
router
.route("/")
.get(loginRequired, getArtists) // Get all artists
.post(loginRequired, addArtist); // Create a new artist
router
.route("/:id")
.get(loginRequired, getArtistById) // Get an artist by their id
.put(loginRequired, updateArtist) // Update an artist by their id
.delete(loginRequired, deleteArtist); // Delete an artist by their id
router.param("id", async (req, res, next, id) => {
// Check if the id is a valid Object Id
if (mongoose.isValidObjectId(id)) {
// Check to see if artist with valid id exists
const artist = await Artist.findOne({ _id: id });
if (!artist) res.status(400).json({ errors: "Artist not found" });
res.locals.artist = artist;
res.locals.artistId = id;
next();
} else {
res.status(400).json({ errors: "not a valid object Id" });
}
});
module.exports = router;
i personly like to make error handeling more global so i would write something like
constPrettyError = require('pretty-error')
const pe = new PrettyError()
const errorHandler = (err, req, res, next) => {
if (process.env.NODE_ENV !== 'test') {
console.log(pe.render(err))
}
return res
.status(err.status || 500)
.json({ error: { message: err.message || 'oops something went wrong' } })
}
module.exports = errorHandler
as a handler
the in your index / server file
app.use(errorHandler)
then in your handlers just
} catch (err) {
next(err);
}
as an example
if (!artist) next({ message: "Artist not found" ,status:404 });
also, note that you can customize this error handler to switch case (or object) a custom error per status as well if you want
const errorHandler = (err, req, res, next) => {
if (process.env.NODE_ENV !== 'test') {
console.log(pe.render(err))
}
const messagePerStatus = {
404: 'not found',
401: 'no authorization'
}
const message = messagePerStatus[err.status]
return res
.status(err.status || 500)
.json({
error: { message: message || err.message || 'oops something went wrong' }
})
}
then just
if (!artist) next({status:404 });
I also agree with answer by Asaf Strilitz but still need to show what i do in my projects
Create a custom error class
AppError.js
class AppError extends Error {
constructor(statusCode, message) {
super();
// super(message);
this.statusCode = statusCode || 500 ;
this.message = message || "Error Something went wrong";
}
}
module.exports = AppError;
Create an error handling middleware
errors.js
const AppError = require("../helpers/appError");
const errors = (err, req, res, next) => {
// console.log(err);
let error = { ...err };
error.statusCode = error.statusCode;
error.message = error.message;
res.status(error.statusCode).json({
statusCode: err.statusCode,
message: err.message,
});
};
exports.errors = errors;
Create a middleware to validate object id
validateObjectId.js
const mongoose = require("mongoose");
const AppError = require("appError");
module.exports = function (req, res, next) {
const { _id } = req.params;
if (_id && !mongoose.Types.ObjectId.isValid(_id)) {
throw new AppError(422, "Invalid ID field in params");
}
next();
};
In your app.js
const { errors } = require("errors");
// At the end of all middlewares
// Error Handler Middleware
app.use(errors);
In your routes file
const express = require("express");
const router = express.Router();
const mongoose = require("mongoose");
const Artist = require("../models/Artist");
const loginRequired = require("../middleware/loginRequired");
const validateId = require("validateObjectId");
const {
getArtists,
addArtist,
getArtistById,
updateArtist,
deleteArtist,
} = require("../controllers/artistController");
// Your routes
router
.route("/:id")
.get(validateId, loginRequired, getArtistById) // Get an artist by their id
.put(validateId, loginRequired, updateArtist) // Update an artist by their id
.delete(validateId, loginRequired, deleteArtist); // Delete an artist by their id
module.exports = router;
Now regarding findById method being repeated i dont see anything bad in that as it is specific to database call still you can introduce a staic method on model itself or create a single method on cntroller but still need to check if it returns the found object or not and handle the error on that.
I am trying to run a delete request but it is not working, I have used the exact same logic on another project exactly like it and it works there.
Here is the route file which includes the delete request as well as the post request that does indeed work
const express = require("express");
const router = express.Router();
const User = require("../models/users");
const cardSchema = require("../models/card");
//add card request
router.post("/:id/addcard", getUser, async (req, res) => {
try {
if (req.body != null) {
const newCard = new cardSchema({
name: req.body.name,
cardNumber: req.body.cardNumber,
ccv: req.body.ccv,
expiration: req.body.expiration,
});
res.user.cardInfo.push(newCard);
}
const updatedCardInfo = await res.user.save();
return res.status(200).json(updatedCardInfo);
} catch (error) {
return res.status(400).json({ message: error.message });
}
});
//delete card request
router.delete("/:id/deletecard", getUser, async (req, res) => {
if (req.body !== null) {
res.user.cardInfo.remove(req.body);
}
try {
const updatedUser = await res.user.save();
res.status(200).json(updatedUser);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
//get user middleware
async function getUser(req, res, next) {
let user;
try {
user = await User.findById(req.params.id);
if (user == null) {
return res.status(404).json({ message: "Cannot find user" });
}
} catch (error) {
return res.status(500).json({ message: error.message });
}
res.user = user;
next();
}
module.exports = router;
I have triple checked that I am using the correct URL and passing in the correct information in the req.body. I recieved the users information after calling the delete request but just does not remove the card information. I have also checked in my database that it is 'cardInfo' so there is no spelling mistake there either.
I have an express backend developed for my app. Upon posting a request to my app route I get an error
"TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be one of
type string or Buffer."
This error occurs when trying to access a function through the request route.
Here is the code
app.post('/api/orderEmail', (req, res) => {
//TODO
console.log('Backend breakpoint');
console.log(req.body);
sendEmail(req.body.mail, req.body.name, "thanks"); // function call that generates error
});
sendEmail's definition goes by:
const sendEmail = (to, name, type) => {
const smtpTransport = mailer.createTransport({
service: "Gmail",
auth: {
user: "xxusernamexx",
pass: "xxpasswordxx"
}
})
const mail = getEmailData(to, name, type)
smtpTransport.sendMail(mail, function(error, response) {
console.log("sendmail breakpoint");
if(error) {
console.log(error)
} else {
console.log( " email sent successfully")
}
smtpTransport.close();
})
}
I'm trying to verify the user's password using bcrypt. But, unfortunately, my post-request does not work, it just loads for a long time and that's it.
I have a model user.js with this code:
UserSchema.methods.comparePasswords = function (password) {
return bcrypt.compare(password, this.password);
};
And i have a controller auth.js with this code:
export const signin = async (req, res, next) => {
const { login, password } = req.body;
const user = await User.findOne({ login });
if (!user) {
return next({
status: 400,
message: 'User not found'
});
}
try {
const result = await user.comparePasswords(password);
} catch (e) {
return next({
status: 400,
message: 'Bad Credentials'
});
}
req.session.userId = user._id;
req.json(user);
};
The handling of incorrect input works well and the server returns false messages for me, but does not process the correct input.