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);
};
route file
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './uploads');
},
filename: function (req, file, cb) {
cb(null, new Date().toISOString() + file.originalname);
},
});
const fileFilter = (req, file, cb) => {
// reject a file
if (file.mimetype === 'image/jpg' || file.mimetype === 'image/png') {
cb(null, true);
} else {
cb(null, false);
}
};
const upload = multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 * 5,
},
fileFilter: fileFilter,
});
router.post('/hiring', upload.single('resume'), (req, res, next) => {
console.log(req.file);
const hiring = new Hiring({
_id: new mongoose.Types.ObjectId(),
username: req.body.username,
email: req.body.gender,
mobile: req.body.mobile,
resume: req.file.path,
});
hiring
.save()
.then((result) => {
console.log(result);
res.status(200).json({
message: 'Created user successfully',
createdUser: {
_id: result._id,
username: result.username,
email: result.email,
mobile: result.mobile,
resume: result.resume,
},
});
})
.catch((err) => {
console.log(err);
res.status(500).json({
error: err,
});
});
});
I am trying to post data in database through postman but it is getting error 'path undefined'. I tried to change folder path like './uploads/', '/uploads', 'uploads/' and 'uploads' but the problem is not solving.
error
TypeError: Cannot read properties of undefined (reading 'path')
please give the solution for this problem.
It appears req.file is undefined. which itself meant you are not uploading file via postman.
You have to attach file in postman with 'resume' as keyword after selecting mutlipart in body section.
check this screenshot
With dothttp
It would look like this
POST 'http://localhost:3000/hiring'
multipart(
'resume'< '<replace path to the resume here>',
)
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);
}
};
Upload and set default controller functions are working perfectly. However, we are trying to implement delete image from Cloudinary as well. How can it be done?
In the documentation it was confusing. Here is the code:
const cloudinary = require('cloudinary');
const HttpStatus = require('http-status-codes');
const User = require('../models/userModels');
cloudinary.config({
cloud_name: 'name',
api_key: 'key',
api_secret: 'secret'
});
module.exports = {
UploadImage(req, res) {
cloudinary.uploader.upload(req.body.image, async result => {
await User.update(
{
_id: req.user._id
},
{
$push: {
images: {
imgId: result.public_id,
imgVersion: result.version
}
}
}
)
.then(() =>
res
.status(HttpStatus.OK)
.json({ message: 'Image uploaded successfully' })
)
.catch(err =>
res
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.json({ message: 'Error uploading image' })
);
});
},
DeleteImage(req, res) {
cloudinary.uploader.destroy(req.params.image, async result => {
await User.update(
{
_id: req.user._id
},
{
$pull: {
images: {
imgId: result.public_id,
imgVersion: result.version
}
}
}
)
.then(() =>
res
.status(HttpStatus.OK)
.json({ message: 'Image deleted successfully' })
)
.catch(err =>
res
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.json({ message: 'Error deleting image' })
);
});
},
async SetDefaultImage(req, res) {
const { imgId, imgVersion } = req.params;
await User.update(
{
_id: req.user._id
},
{
picId: imgId,
picVersion: imgVersion
}
)
.then(() =>
res.status(HttpStatus.OK).json({ message: 'Default image set' })
)
.catch(err =>
res
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.json({ message: 'Error occured' })
);
}
};
We are using Node.js Express with Mongoose. How can we include extra function here that will remove images?
There are two options to delete an image from cloudinary:
By using the admin API. For example in Node:
cloudinary.v2.api.delete_resources(['image1', 'image2'],
function(error, result){console.log(result);});
Using our upload API:
cloudinary.v2.uploader.destroy('sample', function(error,result) {
console.log(result, error) });
Please note that using our admin API is rate limited and you might want to use the second option.
it just because your req.params.image is like https:https://res.cloudinary.com/your/image/upload/v1663358932/Asset_2_bdxdsl.png
instead write your delete request like so :
cloudinary.v2.uploader.destroy('Asset_2_bdxdsl', function(error,result) {
console.log(result, error) })
ps: Asset_2_bdxdsl is your image name without prefix .png !!
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.