When using Multer in an Express application, if maxCount for file uploads is set to 1:
Expected Behavior: When a single file is uploaded, req.files should be populated with the file, and the corresponding URL should be generated. Actual Behavior: When a single file is uploaded, req.files is either not populated correctly or is missing, leading to no URL being generated. However, when two files are uploaded, the URL for the first file is generated correctly.
//Controller Logic
// Allows a user to submit basic information during their first login
async function submitBasicInfo(req, res) {
const userId = req.user.userId; // Extracted from JWT by authentication middleware
const role = req.user.role; // Extracted from JWT by authentication middleware
try {
const user = await User.findById(userId);
if (!user) {
return res.status(404).json({ message: "User not found" });
}
// Log to verify incoming files
console.log("Files received:", req.files);
// Existing URLs
const existingProfilePhotoUrls = user.profilePhotos || [];
const existingProfileVideoUrls = user.profileVideos || [];
const existingProfileDocumentUrls = user.profileDocuments || [];
// New URLs
const newProfilePhotoUrls = req.files.profilePhotos
? req.files.profilePhotos.map((file) =>
path.join("profileUploads", role, userId.toString(), file.filename)
)
: [];
const newProfileVideoUrls = req.files.profileVideos
? req.files.profileVideos.map((file) =>
path.join("profileUploads", role, userId.toString(), file.filename)
)
: [];
const newProfileDocumentUrls = req.files.profileDocuments
? req.files.profileDocuments.map((file) =>
path.join("profileUploads", role, userId.toString(), file.filename)
)
: [];
// Combine existing and new URLs
const updatedProfilePhotoUrls =
existingProfilePhotoUrls.concat(newProfilePhotoUrls);
const updatedProfileVideoUrls =
existingProfileVideoUrls.concat(newProfileVideoUrls);
const updatedProfileDocumentUrls = existingProfileDocumentUrls.concat(
newProfileDocumentUrls
);
// Log the constructed URLs for debugging
console.log(
`Backend - Constructed profilePhotoUrls: ${updatedProfilePhotoUrls}`
);
console.log(
`Backend - Constructed profileVideoUrls: ${updatedProfileVideoUrls}`
);
console.log(
`Backend - Constructed profileDocumentUrls: ${updatedProfileDocumentUrls}`
);
// Update user with the basic information from request body
const updatedUser = await User.findByIdAndUpdate(
userId,
{
$set: {
profileInfo: req.body,
profilePhotos: updatedProfilePhotoUrls,
profileVideos: updatedProfileVideoUrls,
profileDocuments: updatedProfileDocumentUrls,
isFirstLogin: false,
onboardingState: "basicInfoComplete", // Update the state to basicInfoComplete
},
},
{ new: true }
);
// Log the stored URLs and role for debugging
console.log(`Stored profilePhotos: ${updatedUser.profilePhotos}`);
console.log(`Stored profileVideos: ${updatedUser.profileVideos}`);
console.log(`Stored profileDocuments: ${updatedUser.profileDocuments}`);
console.log(`User role: ${role}`);
// Log onboardingState for debugging
console.log(
`SBI- User ${updatedUser.email} updated onboardingState to: ${updatedUser.onboardingState}`
);
res.status(200).json({
user: updatedUser,
role: updatedUser.role, // Include the role in the response
message: "Basic information updated successfully",
});
} catch (error) {
console.error("Error updating user's basic information:", error);
res.status(500).json({ message: "Internal server error" });
}
}
//Multer Middleware
const multer = require("multer");
const path = require("path");
const fs = require("fs");
// Define upload limits for each file type
const uploadLimits = {
profilePhotos: 1, // Max count for profilePhotos
profileVideos: 1, // Max count for profileVideos
profileDocuments: 1, // Max count for profileDocuments
};
// Define file size limits in bytes (1 MB = 1,000,000 bytes)
const fileSizeLimits = {
profilePhotos: 5 * 1e6, // 5 MB for profile photos
profileVideos: 100 * 1e6, // 100 MB for profile videos
profileDocuments: 10 * 1e6, // 10 MB for documents
};
// Configure storage for multer
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const role = req.user.role;
const userId = req.user.userId.toString();
const uploadPath = path.join(
__dirname,
"..",
"..",
"mediaUploads",
role,
userId
);
// Ensure the upload directory exists
fs.mkdirSync(uploadPath, { recursive: true });
console.log(`Uploading file to: ${uploadPath}`);
cb(null, uploadPath);
},
filename: (req, file, cb) => {
const uniqueSuffix = `${Date.now()}-${Math.round(Math.random() * 1e9)}`;
const fileName = `${file.fieldname}-${uniqueSuffix}${path.extname(
file.originalname
)}`;
console.log(`Saving file as: ${fileName}`);
cb(null, fileName);
},
});
// Multer middleware to handle file upload
const profileUploads = multer({
storage,
limits: {
fileSize: Math.max(...Object.values(fileSizeLimits)), // Max size for the largest allowed file type
files: Object.values(uploadLimits).reduce((a, b) => a + b, 0), // Total files limit
},
}).fields([
{ name: "profilePhotos", maxCount: uploadLimits.profilePhotos },
{ name: "profileVideos", maxCount: uploadLimits.profileVideos },
{ name: "profileDocuments", maxCount: uploadLimits.profileDocuments },
]);
// Middleware to construct and store file URLs
const constructFileUrls = (req, res, next) => {
try {
const userId = req.user.userId.toString();
const role = req.user.role;
const basePath = "profileUploads";
// Initialize arrays to store the file URLs
req.body.profilePhotoUrls = [];
req.body.profileVideoUrls = [];
req.body.profileDocumentUrls = [];
if (req.files) {
// Check for and log received files
console.log("Files received:", req.files);
if (req.files.profilePhotos) {
req.body.profilePhotoUrls = req.files.profilePhotos.map((file) => {
const profilePhotoPath = path.join(
basePath,
role,
userId,
file.filename
);
return profilePhotoPath.replace(/\\/g, "/");
});
console.log(`Profile photos URLs: ${req.body.profilePhotoUrls}`);
}
if (req.files.profileVideos) {
req.body.profileVideoUrls = req.files.profileVideos.map((file) => {
const profileVideoPath = path.join(
basePath,
role,
userId,
file.filename
);
return profileVideoPath.replace(/\\/g, "/");
});
console.log(`Profile videos URLs: ${req.body.profileVideoUrls}`);
}
if (req.files.profileDocuments) {
req.body.profileDocumentUrls = req.files.profileDocuments.map(
(file) => {
const profileDocumentPath = path.join(
basePath,
role,
userId,
file.filename
);
return profileDocumentPath.replace(/\\/g, "/");
}
);
console.log(`Profile documents URLs: ${req.body.profileDocumentUrls}`);
}
}
next();
} catch (error) {
console.error("Error in constructFileUrls middleware:", error);
res
.status(500)
.json({ error: "Internal server error during URL construction" });
}
};
// Middleware to validate file upload limits
const validateFileUploads = (req, res, next) => {
const errors = [];
const totalFiles = Object.keys(uploadLimits).reduce((count, field) => {
return count + (req.files[field] ? req.files[field].length : 0);
}, 0);
if (totalFiles > Object.values(uploadLimits).reduce((a, b) => a + b, 0)) {
errors.push(
`Total file upload limit exceeded. Allowed: ${Object.values(
uploadLimits
).reduce((a, b) => a + b, 0)}, Provided: ${totalFiles}`
);
}
for (const [field, limit] of Object.entries(uploadLimits)) {
if (req.files[field] && req.files[field].length > limit) {
errors.push(`Field "${field}" exceeds its limit of ${limit} files.`);
}
}
if (errors.length > 0) {
return res.status(400).json({ errors });
}
next();
};
// Error-handling middleware for Multer errors
const multerErrorHandler = (err, req, res, next) => {
if (err instanceof multer.MulterError) {
console.error("Multer Error:", err);
let message;
if (err.code === "LIMIT_UNEXPECTED_FILE") {
message = `Unexpected file field: ${err.field}`;
} else if (err.code === "LIMIT_FILE_SIZE") {
message = `File size limit exceeded for field: ${err.field}`;
} else {
message = "File upload error";
}
return res.status(400).json({ message, error: err.message });
}
console.error("Unexpected Error:", err);
res.status(500).json({ message: "Something broke!", error: err.message });
};
module.exports = {
profileUploads,
constructFileUrls,
validateFileUploads,
multerErrorHandler,
};
router.post(
"/submit-basic-info",
authenticate,
profileUploads,
constructFileUrls,
validateFileUploads,
multerErrorHandler,
submitBasicInfo
);
PostMan Keys and Values
When using Multer in an Express application, if maxCount for file uploads is set to 1:
Expected Behavior: When a single file is uploaded, req.files should be populated with the file, and the corresponding URL should be generated. Actual Behavior: When a single file is uploaded, req.files is either not populated correctly or is missing, leading to no URL being generated. However, when two files are uploaded, the URL for the first file is generated correctly.
req.fileinstead ofreq.files?