I'm attempting to send an image from my react-native frontend (via an Android phone) to my node.js/express backend, where it is meant to be processed by Multer, and then uploaded to Cloudinary (this last bit being irrelevant to the question). When using postman, this works perfectly fine, and the image goes through and gets uploaded, however, when making my own post request with axios (and I also tried using fetch), it fails, with Multer seemingly not recognising that there is a file in the request. Here is my frontend code:
const uploadProfileImage = async ()=>{
const formData = new FormData();
//console.log(profileImage);
const image = {
uri: profileImage,
type: mime.getType(profileImage),
name: new Date() + "_pfp",
} as any
formData.append('pfp', {
uri: profileImage,
type: mime.getType(profileImage),
name: new Date() + "_pfp",
});
try {
const res = await client.post('/upload-pfp', formData, {headers:{
Accept: 'application/json',
'Content-Type': 'multipart/form-data',
authorization: 'JWT ...',
}});
console.log(res.data);
} catch (error) {
console.error('Upload failed:', {
message: error.message,
status: error.response?.status,
data: error.response?.data
});
}
}
The post request handler runs as below:
router.post('/upload-pfp', isAuth, uploads.single('pfp'), uploadPfp)
Where multer is set up as follows (and uploadPfp is irrelevant as of now, as it is not reaching that stage with a req.file present, as verified by adding another middleware function)
const multer = require('multer');
const storage = multer.diskStorage({});
const fileFilter = (req, file, cb)=>{
console.log("first")
if(file.mimetype.startsWith('image')){
cb(null, true);
}else{
cb('Invalid image file', false);
}
}
const uploads = multer({storage, fileFilter})
I have attempted to use memoryStorage instead of diskStorage, and have also attempted to specify storage locations and names and maximum sizes and the like, but to no avail. The authorisation code, in case that helps at all, is below:
exports.isAuth = async(req, res, next)=>{
if(req.headers && req.headers.authorization){
const token = req.headers.authorization.split(' ')[1];
try {
const decode = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findById(decode.userId);
if(!user){
return res.json({success: false, message: "Unauthorized access"});
}
req.user = user;
next();
} catch (error) {
if(error.name == 'JsonWebTokenError'){
return res.json({success: false, message: "Unauthorized access"});
}
if(error.name == 'TokenExpiredError'){
return res.json({success: false, message: "Session expired, try signing in"});
}
res.json({success: false, message: "Internal server error"});
}
} else {
res.json({success: false, message: "Unauthorized access"})
}
}
I have managed to solve my issue, and it seems to be based on how Multer and FormData interact over the "name" field. Indeed, it is not enough for the first argument when appending the image to the FormData to be 'pfp', but also (or only, I haven't checked), the name within the pfp object itself must be 'pfp' for it to be recognised as the correct file by Multer, as below:
formData.append('pfp', {
uri: profileImage,
type: 'image/jpeg',
name: 'pfp',
});
I then changed my backend code to deal with the renaming for uniqueness:
const storage = multer.diskStorage({filename: (req,file,cb)=>{
cb(null, Date.now()+"_pfp.jpg");
}});