Express-validator not executing after multer function - node.js

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:
call 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,
validate other parameters in middleware
I have noticed you haven't added 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()
],
middleware callback function, here req will provide all body parameters and file object that is uploaded,
just console 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,
handle file extension error using 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);
}
};

Related

how to upload image files to server and MongoDB

React.js Node.js Express.js Axios Multer MongoDB Mongoose
Wrote this form to register users, the user enters name, email, password, and a file for later use as profile pic/Avatar.
When i tested the form on Postman the opposite has happened, the file was uploaded to the public/uploads/images folder but it didnt post to the user with the rest of the registration data i entered.
But when i tested the form on localhost domain the image filename was saved on the user's Avatar value MongoDB nut was'nt uploaded to the servers folder i chose to upload my uploaded files.
wanted end result:
to register user to websites database with all parameters, uploading the image to the server and image filename to users avatar data
Register Route
router.post("/register", upload.single("avatar"), async (req, res) => {
try {
const validatedValue = await validateRegisterSchema(req.body);
const user = await findUserByEmail(validatedValue.email);
if (user) throw "try different email";
const hashedPassword = await createHash(validatedValue.password);
validatedValue.password = hashedPassword;
await createNewUser(validatedValue);
res.status(201).json({ msg: "user created"});
} catch (error) {
res.status(400).json({ error });
}
});
Multer
destination: (req, file, cb) => {
cb(null, "./public/uploads/images/");
},
filename: function (req, file, cb) {
crypto.pseudoRandomBytes(16, function (err, raw) {
if (err) return cb(err);
cb(null, file.originalname);
});
},
});
const fileFilter = (req, file, cb) => {
if (file.mimetype === "image/jpeg" || file.mimetype === "image/png") {
cb(null, true);
} else {
cb(null, false);
}
};
const upload = multer({
storage,
limit: {
fileSize: 1024 * 1024 * 10,
},
fileFilter,
});
User Model
const Schema = mongoose.Schema;
const usersSchema = new Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
wishList: { type: Array },
isAdmin: { type: Boolean, default: false },
avatar: { type: String },
});
const Users = mongoose.model("users", usersSchema);
const createNewUser = (userData) => {
const newUser = new Users(userData);
return newUser.save();
Solved
prior code:
const handleAvatarChange = (ev) => {
let newUserInput = JSON.parse(JSON.stringify(userInput));
newUserInput[ev.target.name] = ev.target.files[0].name;
setUserInput(newUserInput);
};
sulution:
const handleAvatarChange = (ev) => {
let newUserInput = JSON.parse(JSON.stringify(userInput));
newUserInput[ev.target.name] = ev.target.files[0]; // removed ".name" left it as file object
setUserInput(newUserInput);
};

Upload multiple files in node.js using multer

I am creating a node.js app in which there will be an option of uploading the documents.
The front-end Scenario: There will be three fields 1) profile picture 2) user's ID card no 1 (eg. PAN card) 3) user's Id card no 2 (eg. AADHAR card) -> this all field will be in a single page
Now the concern is whenever user uploads the documents I want to save it in a such a way that profile_picture should be considered as 0, user's ID card no 1 should be considered as 1 and so on.
The user schema:
const userSchema = new mongoose.Schema(
{
profile_picture: { type: Array },
documents: {
type: Array,
},
...
{
timestamps: true,
}
);
The uploadDocument service function:
exports.uploadDocuments = async (_id, file) => {
let document = await User.findByIdAndUpdate(
{ _id },
{
$set: { registration_process_value: 99 },
$push: file, //The actual problem is here
},
{ new: true }
);
return document;
};
the controller:
exports.uploadDocuments = catchAsync(async (req, res) => {
try {
check_user = await getUser.getUser(req.data.id);
if (!check_user) return res.status(404).json({ message: "User not found" });
if (check_user.registration_process_value >= 4) {
let uploadDocument = await userService.uploadDocuments(
check_user.id,
req.files
);
return res.status(200).json({ data: uploadDocument });
}
return res.status(404).json({ message: "You are not a verified user" });
} catch (error) {
return res.status(400).json({ message: error });
}
});
The multer function:
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "./src/uploads/");
},
filename: function (req, file, cb) {
cb(
null,
file.fieldname + "-" + Date.now() + path.extname(file.originalname)
);
},
});
var upload = multer({
storage: storage,
fileFilter: function (req, file, cb) {
var filetypes = /jpeg|jpg|png|pdf/;
var mimetype = filetypes.test(file.mimetype);
var extname = filetypes.test(path.extname(file.originalname).toLowerCase());
if (mimetype && extname) {
return cb(null, true);
}
cb("Please upload valid file");
},
}).fields([
{ name: "documents", maxCount: 2 },
{ name: "profile_picture", maxCount: 1 },
]);
I literally have no idea how to accomplish that. Can anyone assist me here?

How can i if dont send image implement default image on my user?

Hi everyone My users have profile picture but its not required,I dont want. So I need if user send me image while register or updated i take this picture and save, so i can this. My problem if user dont send me image i cant give default image
Its my controller code :
//User Register Controller
const register = async (req, res, next) => {
try {
const {
name,
surname,
username,
phoneNumber,
email,
password,
gender,
profilPicture,
birtDate,
} = req.body;
bcrypt.hash(password, 8, async (err, hash) => {
try {
const user = new User({
name,
surname,
username,
phoneNumber,
email,
password: hash,
gender,
profilPicture: 'http://localhost:4000/' + req.file.path || 'http://localhost:4000/public/images/profilePictures/defaultProfilePicture.png',
birtDate,
});
const createdUser = await user.save();
const token = await createToken(createdUser);
res.status(200).json({ token });
} catch (err) {
res.json(err);
}
});
} catch (error) {
res.json({ message: error.message });
}
};
It's my middleware multer :
const multer = require('multer');
const stor = multer.diskStorage({
destination:function(req,file,cb) {
cb(null,'./public/images/profilePictures')
},
filename:function(req,file,cb){
cb(null,'profilePicture-'+new Date().toISOString().replace(/:/g, '-')+file.originalname);
}
})
const fileFilter = (req, file, cb) => {
if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') {
cb(null, true);
} else {
cb(null, false);
}
};
const upload = multer({
storage: stor,
limits: {
fileSize: 1024 * 1024 * 5,
},
fileFilter: fileFilter,
});
module.exports = upload;
My route :
router.post('/register', upload.single('profilPicture'), userController.register);
One way you could do it is set a default within your model
const mongoose = require("mongoose")
const UserSchema = new mongoose.Schema({
profilePicture: {
type: String,
default: 'defaultProfilePicture.png'
}
})
module.exports = mongoose.model("Users", UserSchema)
This way if the user didnt upload a profile picture it would be set to defaultProfilePicture.png
You wouldnt need
profilPicture: 'http://localhost:4000/' + req.file.path || 'http://localhost:4000/public/images/profilePictures/defaultProfilePicture.png',
Just
profilPicture: 'req.file.path'
saving the full URL is never a good idea because you might choose to change it in the future like to remote file store or host your project then the URL will no longer be http://localhost:4000/. You'd ideally save the images unique name and extension.
just try like this :
profilPicture: (req.file) ? 'http://localhost:4000/' + req.file.path : 'http://localhost:4000/public/images/profilePictures/defaultProfilePicture.png';

Fake Path File Upload and Retrieve

I am uploading some personal information to Mongo DB via Express Server(Node) and React. The problem is the files aren't uploaded to the 'uploads directory' on my server and on the DB i can see a fake path of the document with file name. How can I retrieve the file to display to user? I am able to retrieve the other personal information
Below are the codes
This is upload middleware
const path = require('path')
const multer = require('multer')
var storage = multer.diskStorage({
destination: function(req, file, cb){
cb(null, __dirname + '/uploads')
},
filename: function(req, file, cb){
let ext = path.extname(file.originalname)
cb(null, Date.now() + ext)
}
})
var upload = multer ({
storage: storage,
fileFilter: function(req, file, callback){
if(
file.mimetype == "image/png" ||
file.mimetype == "document/pdf" ||
file.mimetype == "document/docx"
){
callback(null, true)
}
else{
console.log('Only png, pdf and Docx FILES ALLOWED')
callback(null, false)
}
},
limits: {
fileSize: 1024 * 1024 * 2
}
})
This is Add Project controller
exports.add = (req, res, next) =>{
const { title, category, duration, durationSys, description, budget, addedBy, active,avatar} = req.body
let newProject = new Project({
title,
category,
duration,
durationSys,
description,
budget,
addedBy,
active,
avatar
})
if(req.file){
newProject.avatar = req.file.path
}
newProject.save()
.then(response =>{
res.json({
message: 'Project added successfully'
})
.catch(error =>{
res.json({
message: 'An error ocurred. Try again'
})
})
})
};
This is the route
router.post('/addproject', upload.single("avatar"), add)
Frontend
const Project= ({history}) => {
const [values, setValues] = useState({
title: '',
category: '',
duration:'',
durationSys: '',
description: 'Describe your project vividly here',
avatar: '',
active:'true',
budget:'',
addedBy: (isAuth().email),
buttonText: 'Post'
});
const { title, category, duration, durationSys,description,avatar,
active,addedBy, budget, buttonText } = values;
const handleChange = name => event => {
setValues({ ...values, [name]: event.target.value } );
};
const clickSubmit = event => {
event.preventDefault();
setValues({ ...values, buttonText: 'Posting Project...' });
console.log(avatar)
axios({
method: 'POST',
url: `${process.env.REACT_APP_API}/addproject`,
data: { title, category, duration, durationSys, description,
avatar, active, addedBy, budget }
})
.then(response => {
console.log('PROJECT ADD SUCCESS', response);
setValues({ ...values, title: '', category: '', duration:
'', durationSys: '',budget: '', description:'',avatar: '',
addedBy: '', buttonText: 'Success' });
toast.success(response.data.message);
})
.catch(error => {
console.log('PROJECT ADD ERROR', error.response.data);
setValues({ ...values, buttonText: 'Try Again' });
toast.error(error.response.data.error);
});
};

file still uploads with multer even after failed form validation

I have a form for adding a product which lets a user input product name, description etc. and also allows an image upload.
In my app.js file which is my entry point, I have:
const fileStorage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads');
},
filename: (req, file, cb) => {
cb(null, new Date().toISOString() + '-' + file.originalname);
}
});
const fileFilter = (req, file, cb) => {
if (
file.mimetype === 'image/png' ||
file.mimetype === 'image/jpg' ||
file.mimetype === 'image/jpeg'
) {
cb(null, true);
} else {
cb(null, false);
}
};
app.use(multer({ storage: fileStorage , fileFilter: fileFilter, limits: { fileSize: 100000} }).single('image'));
Here is my controller:
exports.postAddListing = (req, res, next) => {
const title = req.body.title;
const category = req.body.category;
const description = req.body.description;
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).render('account/add-listing', {
pageTitle: 'Add Item',
path: '/account/add-listing',
errorMessage: errors.array(),
successMessage: null,
oldInput: {
title: title,
description: description
}
});
}
const image = req.file;
const imageUrl = image.path;
const product = new Product({
title: title,
category: category,
description: description,
userId: req.user,
datePosted: Date.now(),
imageUrl: imageUrl
});
product.save()
.then(result => {
const successMessage = req.flash('success', 'Item sucessfully added');
res.redirect('/account/add-listing');
})
.catch(err => console.log(err));
};
I am using express-validator for form validation. The problem I have is that if I for example leave all fields empty but choose an image, validation will fire and I will get error messages but the image will still upload. If form validation fails I don't want the image to upload but not sure how to achieve that.

Resources