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
Storage engines determine where and how uploaded files are stored. Multer provides built-in disk and memory storage engines, with support for custom storage implementations.
Stores uploaded files on the local file system with configurable destination and filename handling.
/**
* Creates a disk storage engine for saving files to the file system
* @param options - Configuration options for disk storage
* @returns DiskStorage instance
*/
const diskStorage = multer.diskStorage(options: DiskStorageOptions);
interface DiskStorageOptions {
/** Directory or function to determine where files are stored */
destination?: string | DestinationFunction;
/** Function to determine the filename for stored files */
filename?: FilenameFunction;
}
type DestinationFunction = (
req: Request,
file: File,
cb: (error: Error | null, destination: string) => void
) => void;
type FilenameFunction = (
req: Request,
file: File,
cb: (error: Error | null, filename: string) => void
) => void;Usage Examples:
const multer = require('multer');
const path = require('path');
// Simple disk storage with static destination
const storage = multer.diskStorage({
destination: './uploads',
filename: (req, file, cb) => {
// Generate unique filename with timestamp
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
}
});
// Dynamic destination based on file type
const dynamicStorage = multer.diskStorage({
destination: (req, file, cb) => {
let folder = 'uploads/';
if (file.mimetype.startsWith('image/')) {
folder += 'images/';
} else if (file.mimetype === 'application/pdf') {
folder += 'documents/';
} else {
folder += 'others/';
}
cb(null, folder);
},
filename: (req, file, cb) => {
// Keep original filename with timestamp prefix
cb(null, Date.now() + '-' + file.originalname);
}
});
// User-specific uploads
const userStorage = multer.diskStorage({
destination: (req, file, cb) => {
const userId = req.user ? req.user.id : 'anonymous';
cb(null, `uploads/users/${userId}/`);
},
filename: (req, file, cb) => {
// Sanitize filename and add timestamp
const sanitized = file.originalname.replace(/[^a-zA-Z0-9.-]/g, '_');
cb(null, Date.now() + '-' + sanitized);
}
});
const upload = multer({ storage: storage });Stores uploaded files in memory as Buffer objects, useful for temporary processing or cloud storage uploads.
/**
* Creates a memory storage engine for keeping files in memory
* @returns MemoryStorage instance
*/
const memoryStorage = multer.memoryStorage();Usage Examples:
const multer = require('multer');
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });
app.post('/process-image', upload.single('image'), (req, res) => {
if (req.file) {
// File is available as Buffer in req.file.buffer
console.log('File size:', req.file.buffer.length);
console.log('File type:', req.file.mimetype);
// Process the buffer (resize, compress, etc.)
processImageBuffer(req.file.buffer)
.then(processedBuffer => {
// Upload to cloud storage, save to database, etc.
return uploadToCloud(processedBuffer, req.file.originalname);
})
.then(cloudUrl => {
res.json({ success: true, url: cloudUrl });
})
.catch(error => {
res.status(500).json({ error: error.message });
});
} else {
res.status(400).json({ error: 'No file uploaded' });
}
});
// Batch processing multiple files in memory
app.post('/batch-process', upload.array('files', 10), (req, res) => {
if (req.files && req.files.length > 0) {
const processPromises = req.files.map(file => {
return processFileBuffer(file.buffer, file.mimetype);
});
Promise.all(processPromises)
.then(results => {
res.json({ processed: results.length, results });
})
.catch(error => {
res.status(500).json({ error: error.message });
});
}
});Different storage engines add different properties to the file object:
// DiskStorage file object
interface DiskStorageFile extends File {
/** Directory where the file was saved */
destination: string;
/** Name of the file within the destination directory */
filename: string;
/** Full path to the saved file */
path: string;
/** Size of the file in bytes */
size: number;
}
// MemoryStorage file object
interface MemoryStorageFile extends File {
/** Buffer containing the entire file */
buffer: Buffer;
/** Size of the file in bytes */
size: number;
}
// Base file properties (available in both storage types)
interface File {
/** Field name from the form */
fieldname: string;
/** Original filename from user's computer */
originalname: string;
/** File encoding type */
encoding: string;
/** MIME type of the file */
mimetype: string;
}You can create custom storage engines by implementing the required interface:
/**
* Custom storage engine interface
* Must implement _handleFile and _removeFile methods
*/
interface StorageEngine {
_handleFile(req: Request, file: File, cb: StorageCallback): void;
_removeFile(req: Request, file: File, cb: RemoveCallback): void;
}
type StorageCallback = (error: Error | null, info?: any) => void;
type RemoveCallback = (error: Error | null) => void;Custom Storage Example:
// Example: Custom storage that saves to a database
function DatabaseStorage(options) {
this.database = options.database;
}
DatabaseStorage.prototype._handleFile = function(req, file, cb) {
const chunks = [];
file.stream.on('data', chunk => chunks.push(chunk));
file.stream.on('error', cb);
file.stream.on('end', () => {
const buffer = Buffer.concat(chunks);
// Save to database
this.database.saveFile({
filename: file.originalname,
mimetype: file.mimetype,
data: buffer,
size: buffer.length
})
.then(result => {
cb(null, {
fileId: result.id,
size: buffer.length,
filename: file.originalname
});
})
.catch(cb);
});
};
DatabaseStorage.prototype._removeFile = function(req, file, cb) {
this.database.deleteFile(file.fileId)
.then(() => cb(null))
.catch(cb);
};
// Usage
const databaseStorage = new DatabaseStorage({ database: myDb });
const upload = multer({ storage: databaseStorage });// Secure filename generation
const secureFilename = (req, file, cb) => {
// Remove potentially dangerous characters
const sanitized = file.originalname.replace(/[^a-zA-Z0-9.-]/g, '_');
// Add timestamp to prevent conflicts
const timestamp = Date.now();
// Limit filename length
const maxLength = 100;
const truncated = sanitized.length > maxLength
? sanitized.substring(0, maxLength)
: sanitized;
cb(null, `${timestamp}-${truncated}`);
};
// Secure destination handling
const secureDestination = (req, file, cb) => {
// Prevent directory traversal
const userId = req.user?.id?.replace(/[^a-zA-Z0-9]/g, '') || 'anonymous';
const safeDestination = path.join('uploads', userId);
// Ensure directory exists
fs.makedirs(safeDestination, { recursive: true }, (err) => {
if (err) return cb(err);
cb(null, safeDestination);
});
};// For high-volume uploads, use disk storage
const highVolumeStorage = multer.diskStorage({
destination: './uploads',
filename: (req, file, cb) => {
// Use crypto for truly unique filenames
const crypto = require('crypto');
const hash = crypto.randomBytes(16).toString('hex');
cb(null, hash + path.extname(file.originalname));
}
});
// For temporary processing, use memory storage with limits
const tempProcessingUpload = multer({
storage: multer.memoryStorage(),
limits: {
fileSize: 10 * 1024 * 1024, // 10MB limit
files: 1 // Single file only
}
});