I have a form in my express application for users to enter some text, and also upload 1-3 photos. I'm handling the file uploads to my S3 bucket with multer, and I'm validating the rest of the form with express-validator
.
This makes my POST route look like:
router.post("/list-product", listLimiter, function (req, res) {
singleUpload(req, res, function (err) {
// if any multer errors, redirect to form
if (err) {
res.redirect(
"list-product" +
"?error=Image error, please make sure your file is JPG or PNG"
);
return;
}
// if no multer errors, validate rest of form
});
});
I'm having issues integrating express-validator
. I've been stuck on this problem for a few days now, and I think I'm getting close. My code below will catch the multer error, and it will create a new Product
if all the inputs are filled out. So what's happening is my express-validator
isn't catching the errors here if (!errors.isEmpty()) { // handle errors
and its just skipping it and going straight to else { let product = new Product({
. I know this because when I leave the inputs empty, it throws a mongoose error of missing schema paths.
let upload = require("../controllers/uploadController");
const singleUpload = upload.single("image");
router.post("/list-product", listLimiter, function (req, res, next) {
singleUpload(req, res, function (err) {
// if any multer errors, redirect to form
if (err) {
res.redirect(
"list-product" +
"?error=Image error, please make sure your file is JPG or PNG"
);
return;
}
// if no multer errors, validate rest of form
body("productName")
.trim()
.isLength({ min: 1 })
.withMessage("Please enter the name of your product"),
body("productPrice")
.isNumeric()
.withMessage("Please enter a valid price"),
body("productCategory")
.trim()
.isLength({ min: 1 })
.withMessage("Please select the category of your product"),
body("productDescription")
.trim()
.isLength({ min: 50 })
.withMessage("Minimum 50 characters")
.isLength({ max: 500 })
.withMessage("Maximum 500 characters"),
body("businessName")
.trim()
.isLength({ min: 1 })
.withMessage("Please enter the name of your business"),
body("website")
.trim()
.isURL()
.withMessage("Please enter the URL for your product or business");
check("*").escape();
const errors = validationResult(req);
let errArray = errors.array();
if (!errors.isEmpty()) {
res.render("list", {
form: req.body,
errors: errArray,
msg: "Please check the form for errors",
option: req.body.productCategory,
});
return;
} else {
let product = new Product({
business: req.body.businessName,
productName: req.body.productName,
category: req.body.productCategory,
price: req.body.productPrice,
description: req.body.productDescription,
website: req.body.website,
imageURL:
"https://mybucket.s3-us-west-2.amazonaws.com/" + req.imageName,
imageURL2:
"https://mybucket.s3-us-west-2.amazonaws.com/" + req.imageName,
});
product.save(function (err) {
if (err) {
console.log(err);
return next(err);
}
res.redirect("/list-product");
});
}
});
});
I would truly appreciate any help or advice since I have been stuck on this for a few days and am feeling really stupid!..
I will include one final block of code, this is my express-validator
function that works when I'm just validating the text inputs, so I know this approach does work.. I'm just having a real tough time combining it with the multer function
exports.list__post = [
body("productName")
.trim()
.isLength({ min: 1 })
.withMessage("Please enter the name of your product"),
body("productPrice")
.isNumeric()
.withMessage("Please enter a valid price"),
body("productCategory")
.trim()
.isLength({ min: 1 })
.withMessage("Please select the category of your product"),
body("productDescription")
.trim()
.isLength({ min: 50 })
.withMessage("Minimum 50 characters")
.isLength({ max: 500 })
.withMessage("Maximum 500 characters"),
body("businessName")
.trim()
.isLength({ min: 1 })
.withMessage("Please enter the name of your business"),
body("website")
.trim()
.isURL()
.withMessage("Please enter the URL for your product or business"),
check("*").escape(),
(req, res, next) => {
const errors = validationResult(req);
let errArray = errors.array();
if (!errors.isEmpty()) {
console.log(req.body)
res.render("list", {
form: req.body,
errors: errArray,
msg: "Please check the form for errors",
option: req.body.productCategory,
});
return;
} else {
let product = new Product({
business: req.body.businessName,
productName: req.body.productName,
category: req.body.productCategory,
price: req.body.productPrice,
description: req.body.productDescription,
website: req.body.website,
imageURL: "https://mybucket.s3-us-west-2.amazonaws.com/" + req.imageName,
imageURL2:"https://mybucket.s3-us-west-2.amazonaws.com/" + req.imageName,
});
product.save(function (err) {
if (err) {
console.log(err);
return next(err);
}
res.redirect("/list-product");
});
}
},
];
Update:
// uploadController.js
const aws = require("aws-sdk");
const multer = require("multer");
const multerS3 = require("multer-s3");
const crypto = require("crypto");
require("dotenv").config();
// config aws
aws.config.update({
secretAccessKey: process.env.SECRETACCESSKEY,
accessKeyId: process.env.ACCESSKEYID,
region: "us-west-2",
});
const s3 = new aws.S3();
const fileFilter = (req, file, cb) => {
if (file.mimetype === "image/jpeg" || file.mimetype === "image/png") {
cb(null, true);
} else {
cb(new Error("Invalid format, only JPG and PNG"), false);
}
};
const upload = multer({
fileFilter: fileFilter,
limits: { fileSize: 1024 * 1024 },
storage: multerS3({
s3: s3,
bucket: "mybucket",
acl: "public-read",
metadata: function (req, file, cb) {
cb(null, { fieldName: file.fieldname });
},
key: function (req, file, cb) {
req.imageName = crypto.randomBytes(16).toString("hex");
cb(null, req.imageName);
},
}),
});
module.exports = upload;
Quick Fixes:
singleUpload
in middleware, and here you are uploading single file using upload.single()
, so you need to remove multiple
attribute from view file tag, if you want to upload multiple file then use upload.array('field name', total count of files)
router.post("/list-product",
singleUpload,
businessName
field in form, so this will return with error, you can add it in form or remove validation from here, and also in schema. [
body("productName").trim().isLength({ min: 1 }).withMessage("Please enter the name of your product"),
body("productPrice").isNumeric().withMessage("Please enter a valid price"),
body("productCategory").trim().isLength({ min: 1 }).withMessage("Please select the category of your product"),
body("productDescription").trim().isLength({ min: 50 }).withMessage("Minimum 50 characters").isLength({ max: 500 }).withMessage("Maximum 500 characters"),
body("businessName").trim().isLength({ min: 1 }).withMessage("Please enter the name of your business"),
body("website").trim().isURL().withMessage("Please enter the URL for your product or business"),
check("*").escape()
],
req
will provide all body parameters and file object that is uploaded,req.body
and using req.file
will return all details of file that is uploaded in S3, including filename and location, i suggest you to use file name and location from this object, I have console it already,req.fileTypeInvalid
, that we have passed from fileFitler function (req, res, next) {
console.log(req.body, req.file);
// FILE EXTENSION ERROR
if (req.fileTypeInvalid) {
res.redirect("list-product" + "?error=" + req.fileTypeInvalid);
return;
}
const errors = validationResult(req);
if (!errors.isEmpty()) {
let errArray = errors.array();
let errorsObj = {}; // access errors indiviually
errArray.map((item) => {
const id = item.param;
delete item.param;
errorsObj[id] = item;
});
res.render("list", {
form: req.body,
errors: errorsObj,
msg: "Please check the form for errors",
option: req.body.productCategory,
});
return;
}
let product = new Product({
business: req.body.businessName,
productName: req.body.productName,
category: req.body.productCategory,
price: req.body.productPrice,
description: req.body.productDescription,
website: req.body.website,
imageURL: "https://mybucket.s3-us-west-2.amazonaws.com/" + req.imageName,
});
product.save(function (err) {
if (err) {
console.log(err);
return next(err);
}
res.redirect("/list-product");
});
});
Combined Final Version of Request:
router.post("/list-product",
singleUpload,
[
body("productName").trim().isLength({ min: 1 }).withMessage("Please enter the name of your product"),
body("productPrice").isNumeric().withMessage("Please enter a valid price"),
body("productCategory").trim().isLength({ min: 1 }).withMessage("Please select the category of your product"),
body("productDescription").trim().isLength({ min: 50 }).withMessage("Minimum 50 characters").isLength({ max: 500 }).withMessage("Maximum 500 characters"),
body("businessName").trim().isLength({ min: 1 }).withMessage("Please enter the name of your business"),
body("website").trim().isURL().withMessage("Please enter the URL for your product or business"),
check("*").escape()
],
function (req, res, next) {
console.log(req.body, req.file);
// FILE EXTENSION ERROR
if (req.fileTypeInvalid) {
res.redirect("list-product" + "?error=Image error, please make sure your file is JPG or PNG");
return;
}
const errors = validationResult(req);
if (!errors.isEmpty()) {
let errArray = errors.array();
let errorsObj = {}; // access errors indiviually
errArray.map((item) => {
const id = item.param;
delete item.param;
errorsObj[id] = item;
});
res.render("list", {
form: req.body,
errors: errorsObj,
msg: "Please check the form for errors",
option: req.body.productCategory,
});
return;
}
let product = new Product({
business: req.body.businessName,
productName: req.body.productName,
category: req.body.productCategory,
price: req.body.productPrice,
description: req.body.productDescription,
website: req.body.website,
imageURL: "https://mybucket.s3-us-west-2.amazonaws.com/" + req.imageName,
});
product.save(function (err) {
if (err) {
console.log(err);
return next(err);
}
res.redirect("/list-product");
});
});
One more correction in fileFilter inside uloadController.js
file, pass error in req.fileTypeInvalid
and return it, it is handled in request,
const fileFilter = (req, file, cb) => {
if (file.mimetype === "image/jpeg" || file.mimetype === "image/png") {
cb(null, true);
} else {
req.fileTypeInvalid = "Invalid format, only JPG and PNG";
cb(null, false, req.fileTypeInvalid);
}
};