Node.js middleware for handling multipart/form-data, primarily used for file uploads
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Comprehensive error handling system for upload failures, validation errors, and limit violations with custom error types and detailed error reporting.
Custom error class for Multer-specific errors with standardized error codes and optional field information.
/**
* Custom error class for Multer-specific upload errors
* @param code - Error code indicating the type of error
* @param field - Optional field name where the error occurred
*/
class MulterError extends Error {
constructor(code: string, field?: string);
/** Error code indicating the specific type of error */
code: string;
/** Field name where the error occurred (if applicable) */
field?: string;
/** Human-readable error message */
message: string;
/** Error class name */
name: string;
}Standard error codes for different types of upload failures:
/** Too many parts in the multipart form */
const LIMIT_PART_COUNT = 'LIMIT_PART_COUNT';
/** File size exceeds the specified limit */
const LIMIT_FILE_SIZE = 'LIMIT_FILE_SIZE';
/** Too many files uploaded */
const LIMIT_FILE_COUNT = 'LIMIT_FILE_COUNT';
/** Field name is too long */
const LIMIT_FIELD_KEY = 'LIMIT_FIELD_KEY';
/** Field value is too long */
const LIMIT_FIELD_VALUE = 'LIMIT_FIELD_VALUE';
/** Too many fields in the form */
const LIMIT_FIELD_COUNT = 'LIMIT_FIELD_COUNT';
/** File uploaded to unexpected field */
const LIMIT_UNEXPECTED_FILE = 'LIMIT_UNEXPECTED_FILE';
/** Field name is missing */
const MISSING_FIELD_NAME = 'MISSING_FIELD_NAME';/**
* Express error handler specifically for multer errors
* @param err - Error object (potentially MulterError)
* @param req - Express request object
* @param res - Express response object
* @param next - Express next function
*/
type MulterErrorHandler = (
err: Error | MulterError,
req: Request,
res: Response,
next: NextFunction
) => void;Usage Examples:
const multer = require('multer');
const upload = multer({
dest: 'uploads/',
limits: {
fileSize: 1024 * 1024 * 2, // 2MB limit
files: 3 // Maximum 3 files
}
});
// Method 1: Handle errors in middleware callback
app.post('/upload', (req, res) => {
upload.single('file')(req, res, (err) => {
if (err instanceof multer.MulterError) {
// Multer-specific error occurred
switch (err.code) {
case 'LIMIT_FILE_SIZE':
return res.status(400).json({
error: 'File too large',
message: `File size exceeds 2MB limit`,
field: err.field
});
case 'LIMIT_FILE_COUNT':
return res.status(400).json({
error: 'Too many files',
message: 'Maximum 3 files allowed'
});
case 'LIMIT_UNEXPECTED_FILE':
return res.status(400).json({
error: 'Unexpected file',
message: `Unexpected file in field: ${err.field}`
});
default:
return res.status(400).json({
error: 'Upload error',
message: err.message,
code: err.code
});
}
} else if (err) {
// Other error (e.g., from fileFilter)
return res.status(400).json({
error: 'Upload failed',
message: err.message
});
}
// No error - upload successful
res.json({
success: true,
file: req.file
});
});
});
// Method 2: Use Express error handler middleware
app.post('/upload-with-middleware', upload.single('file'), (req, res) => {
res.json({
success: true,
file: req.file
});
});
// Global error handler for multer errors
app.use((err, req, res, next) => {
if (err instanceof multer.MulterError) {
const errorResponses = {
'LIMIT_FILE_SIZE': {
status: 413,
message: 'File too large'
},
'LIMIT_FILE_COUNT': {
status: 400,
message: 'Too many files'
},
'LIMIT_FIELD_COUNT': {
status: 400,
message: 'Too many fields'
},
'LIMIT_FIELD_KEY': {
status: 400,
message: 'Field name too long'
},
'LIMIT_FIELD_VALUE': {
status: 400,
message: 'Field value too long'
},
'LIMIT_PART_COUNT': {
status: 400,
message: 'Too many parts'
},
'LIMIT_UNEXPECTED_FILE': {
status: 400,
message: `Unexpected file in field: ${err.field}`
},
'MISSING_FIELD_NAME': {
status: 400,
message: 'Field name missing'
}
};
const errorResponse = errorResponses[err.code] || {
status: 400,
message: 'Upload error'
};
return res.status(errorResponse.status).json({
error: err.code,
message: errorResponse.message,
field: err.field
});
}
// Handle other errors
next(err);
});File filter functions can throw errors to reject files:
const imageFilter = (req, file, cb) => {
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
// Throw error to reject file
cb(new Error('Only image files are allowed'), false);
}
};
const upload = multer({
dest: 'uploads/',
fileFilter: imageFilter
});
app.post('/upload-image', upload.single('image'), (req, res) => {
// This will be handled by error middleware if file filter rejects
res.json({ success: true, file: req.file });
});Storage engines can also generate errors during file handling:
const problematicStorage = multer.diskStorage({
destination: (req, file, cb) => {
// Check if directory is writable
const dest = './uploads';
fs.access(dest, fs.constants.W_OK, (err) => {
if (err) {
cb(new Error('Upload directory not writable'), null);
} else {
cb(null, dest);
}
});
},
filename: (req, file, cb) => {
// Check for duplicate filenames
const filename = file.originalname;
const fullPath = path.join('./uploads', filename);
fs.access(fullPath, fs.constants.F_OK, (err) => {
if (!err) {
// File exists - generate unique name
const timestamp = Date.now();
const ext = path.extname(filename);
const base = path.basename(filename, ext);
cb(null, `${base}-${timestamp}${ext}`);
} else {
cb(null, filename);
}
});
}
});// Comprehensive error handling with logging
const createUploadHandler = (uploadConfig) => {
const upload = multer(uploadConfig);
return (req, res, next) => {
upload.single('file')(req, res, (err) => {
if (err) {
// Log error for debugging
console.error('Upload error:', {
error: err.message,
code: err.code,
field: err.field,
user: req.user?.id,
timestamp: new Date().toISOString(),
ip: req.ip,
userAgent: req.headers['user-agent']
});
// Send appropriate response
if (err instanceof multer.MulterError) {
return res.status(400).json({
success: false,
error: err.code,
message: getErrorMessage(err.code),
field: err.field
});
} else {
return res.status(500).json({
success: false,
error: 'UPLOAD_FAILED',
message: 'File upload failed'
});
}
}
next();
});
};
};
const getErrorMessage = (code) => {
const messages = {
'LIMIT_FILE_SIZE': 'File size exceeds maximum allowed size',
'LIMIT_FILE_COUNT': 'Too many files uploaded',
'LIMIT_FIELD_COUNT': 'Too many form fields',
'LIMIT_FIELD_KEY': 'Field name is too long',
'LIMIT_FIELD_VALUE': 'Field value is too long',
'LIMIT_PART_COUNT': 'Too many parts in multipart form',
'LIMIT_UNEXPECTED_FILE': 'File uploaded to unexpected field',
'MISSING_FIELD_NAME': 'Field name is required'
};
return messages[code] || 'Upload error occurred';
};
// Usage
app.post('/secure-upload',
createUploadHandler({
dest: 'uploads/',
limits: {
fileSize: 5 * 1024 * 1024, // 5MB
files: 1
}
}),
(req, res) => {
res.json({
success: true,
file: req.file
});
}
);For handling errors on the client side:
// Fetch API example
const uploadFile = async (file) => {
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch('/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
if (!response.ok) {
// Handle different error types
switch (result.error) {
case 'LIMIT_FILE_SIZE':
throw new Error('File is too large. Maximum size is 2MB.');
case 'LIMIT_FILE_COUNT':
throw new Error('Too many files. Maximum is 3 files.');
case 'LIMIT_UNEXPECTED_FILE':
throw new Error(`File uploaded to wrong field: ${result.field}`);
default:
throw new Error(result.message || 'Upload failed');
}
}
return result;
} catch (error) {
console.error('Upload error:', error.message);
throw error;
}
};
// Usage in form handler
document.getElementById('uploadForm').onsubmit = async (e) => {
e.preventDefault();
const fileInput = document.getElementById('fileInput');
if (fileInput.files.length === 0) {
alert('Please select a file');
return;
}
try {
const result = await uploadFile(fileInput.files[0]);
alert('Upload successful!');
} catch (error) {
alert(`Upload failed: ${error.message}`);
}
};