Node.js Upload Files - Complete File Upload Guide
File uploads are a common requirement in web applications. Node.js provides several ways to handle file uploads, from basic multipart form data processing to advanced file management with validation and security features.
Understanding File Uploads
HTTP Multipart Form Data
When files are uploaded via web forms, they are sent as multipart/form-data in the HTTP request body. This format allows multiple fields and files to be sent in a single request.
<!-- HTML form for file upload -->
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" multiple>
<input type="text" name="description" placeholder="File description">
<button type="submit">Upload</button>
</form>
Basic File Upload with Express
const express = require('express');
const multer = require('multer');
const path = require('path');
const app = express();
// Configure multer for file uploads
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
cb(null, Date.now() + '-' + file.originalname);
}
});
const upload = multer({ storage: storage });
// Handle file upload
app.post('/upload', upload.single('file'), (req, res) => {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
res.json({
message: 'File uploaded successfully',
file: {
filename: req.file.filename,
originalname: req.file.originalname,
size: req.file.size,
mimetype: req.file.mimetype
}
});
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Using Multer for File Uploads
Installation
npm install multer
Basic Multer Configuration
const multer = require('multer');
// Memory storage (files stored in memory)
const upload = multer({ storage: multer.memoryStorage() });
// Disk storage (files stored on disk)
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
cb(null, Date.now() + '-' + file.originalname);
}
});
const upload = multer({ storage: storage });
File Upload Middleware
// Single file upload
app.post('/upload-single', upload.single('file'), (req, res) => {
console.log(req.file);
res.json({ message: 'Single file uploaded' });
});
// Multiple files upload
app.post('/upload-multiple', upload.array('files', 5), (req, res) => {
console.log(req.files);
res.json({ message: 'Multiple files uploaded' });
});
// Multiple fields with files
app.post('/upload-fields', upload.fields([
{ name: 'avatar', maxCount: 1 },
{ name: 'gallery', maxCount: 8 }
]), (req, res) => {
console.log(req.files);
res.json({ message: 'Files uploaded' });
});
// Any number of files
app.post('/upload-any', upload.any(), (req, res) => {
console.log(req.files);
res.json({ message: 'Files uploaded' });
});
File Validation and Security
File Type Validation
const multer = require('multer');
const path = require('path');
// File filter for validation
const fileFilter = (req, file, cb) => {
// Check file extension
const allowedTypes = /jpeg|jpg|png|gif|pdf|doc|docx/;
const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
// Check MIME type
const mimetype = allowedTypes.test(file.mimetype);
if (mimetype && extname) {
return cb(null, true);
} else {
cb(new Error('Invalid file type. Only images and documents are allowed.'));
}
};
// File size limit (5MB)
const upload = multer({
storage: multer.diskStorage({
destination: 'uploads/',
filename: (req, file, cb) => {
cb(null, Date.now() + '-' + file.originalname);
}
}),
fileFilter: fileFilter,
limits: {
fileSize: 5 * 1024 * 1024 // 5MB
}
});
Advanced File Validation
const multer = require('multer');
const sharp = require('sharp'); // For image processing
const upload = multer({
storage: multer.memoryStorage(),
fileFilter: (req, file, cb) => {
// Validate file type
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new Error('Only image files are allowed'), false);
}
},
limits: {
fileSize: 10 * 1024 * 1024 // 10MB
}
});
// Process uploaded image
app.post('/upload-image', upload.single('image'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No image uploaded' });
}
// Process image with Sharp
const processedImage = await sharp(req.file.buffer)
.resize(800, 600)
.jpeg({ quality: 80 })
.toBuffer();
// Save processed image
const filename = Date.now() + '.jpg';
const filepath = path.join('uploads', filename);
require('fs').writeFileSync(filepath, processedImage);
res.json({
message: 'Image processed and uploaded',
filename: filename,
size: processedImage.length
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
File Storage Options
Local Disk Storage
const multer = require('multer');
const path = require('path');
const fs = require('fs');
// Ensure upload directory exists
const uploadDir = 'uploads';
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
const storage = multer.diskStorage({
destination: (req, file, cb) => {
// Create subdirectory based on file type
let subdir = 'general';
if (file.mimetype.startsWith('image/')) {
subdir = 'images';
} else if (file.mimetype.startsWith('video/')) {
subdir = 'videos';
} else if (file.mimetype.includes('pdf')) {
subdir = 'documents';
}
const fullPath = path.join(uploadDir, subdir);
if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath, { recursive: true });
}
cb(null, fullPath);
},
filename: (req, file, cb) => {
// Generate unique filename
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const ext = path.extname(file.originalname);
const name = path.basename(file.originalname, ext);
cb(null, `${name}-${uniqueSuffix}${ext}`);
}
});
const upload = multer({ storage: storage });
Cloud Storage (AWS S3)
const multer = require('multer');
const AWS = require('aws-sdk');
const multerS3 = require('multer-s3');
// Configure AWS S3
const s3 = new AWS.S3({
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.AWS_REGION
});
// Configure multer for S3
const upload = multer({
storage: multerS3({
s3: s3,
bucket: process.env.S3_BUCKET_NAME,
acl: 'public-read',
key: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, `uploads/${uniqueSuffix}-${file.originalname}`);
},
contentType: multerS3.AUTO_CONTENT_TYPE
}),
fileFilter: (req, file, cb) => {
// Add file validation here
cb(null, true);
},
limits: {
fileSize: 10 * 1024 * 1024 // 10MB
}
});
Complete File Upload Application
Express Server with File Upload
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const sharp = require('sharp');
const app = express();
// Middleware
app.use(express.json());
app.use(express.static('public'));
// Ensure upload directories exist
const uploadDirs = ['uploads', 'uploads/images', 'uploads/documents', 'uploads/videos'];
uploadDirs.forEach(dir => {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
});
// Multer configuration
const storage = multer.diskStorage({
destination: (req, file, cb) => {
let subdir = 'general';
if (file.mimetype.startsWith('image/')) {
subdir = 'images';
} else if (file.mimetype.startsWith('video/')) {
subdir = 'videos';
} else if (file.mimetype.includes('pdf') || file.mimetype.includes('document')) {
subdir = 'documents';
}
cb(null, `uploads/${subdir}`);
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const ext = path.extname(file.originalname);
const name = path.basename(file.originalname, ext);
cb(null, `${name}-${uniqueSuffix}${ext}`);
}
});
const fileFilter = (req, file, cb) => {
const allowedTypes = /jpeg|jpg|png|gif|pdf|doc|docx|mp4|avi|mov/;
const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
const mimetype = allowedTypes.test(file.mimetype);
if (mimetype && extname) {
return cb(null, true);
} else {
cb(new Error('Invalid file type'));
}
};
const upload = multer({
storage: storage,
fileFilter: fileFilter,
limits: {
fileSize: 10 * 1024 * 1024 // 10MB
}
});
// Routes
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
// Single file upload
app.post('/upload', upload.single('file'), (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
res.json({
message: 'File uploaded successfully',
file: {
filename: req.file.filename,
originalname: req.file.originalname,
size: req.file.size,
mimetype: req.file.mimetype,
path: req.file.path
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Multiple files upload
app.post('/upload-multiple', upload.array('files', 10), (req, res) => {
try {
if (!req.files || req.files.length === 0) {
return res.status(400).json({ error: 'No files uploaded' });
}
const files = req.files.map(file => ({
filename: file.filename,
originalname: file.originalname,
size: file.size,
mimetype: file.mimetype,
path: file.path
}));
res.json({
message: 'Files uploaded successfully',
files: files
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Image processing upload
app.post('/upload-image', upload.single('image'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No image uploaded' });
}
if (!req.file.mimetype.startsWith('image/')) {
return res.status(400).json({ error: 'File is not an image' });
}
// Process image
const processedImage = await sharp(req.file.buffer)
.resize(800, 600, { fit: 'inside', withoutEnlargement: true })
.jpeg({ quality: 80 })
.toBuffer();
// Save processed image
const processedFilename = `processed-${req.file.filename}`;
const processedPath = path.join('uploads/images', processedFilename);
fs.writeFileSync(processedPath, processedImage);
res.json({
message: 'Image processed and uploaded',
original: {
filename: req.file.filename,
size: req.file.size
},
processed: {
filename: processedFilename,
size: processedImage.length
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Error handling middleware
app.use((error, req, res, next) => {
if (error instanceof multer.MulterError) {
if (error.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({ error: 'File too large' });
}
if (error.code === 'LIMIT_FILE_COUNT') {
return res.status(400).json({ error: 'Too many files' });
}
}
res.status(500).json({ error: error.message });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
HTML Form for File Upload
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Upload</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.container { max-width: 600px; margin: 0 auto; }
.form-group { margin-bottom: 20px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input[type="file"] { width: 100%; padding: 10px; border: 1px solid #ddd; }
input[type="text"] { width: 100%; padding: 10px; border: 1px solid #ddd; }
button { background: #007acc; color: white; padding: 10px 20px; border: none; cursor: pointer; }
button:hover { background: #005a9e; }
.result { margin-top: 20px; padding: 10px; background: #f5f5f5; border-radius: 5px; }
</style>
</head>
<body>
<div class="container">
<h1>File Upload Demo</h1>
<!-- Single file upload -->
<form id="singleUploadForm" enctype="multipart/form-data">
<h2>Single File Upload</h2>
<div class="form-group">
<label for="file">Choose file:</label>
<input type="file" id="file" name="file" required>
</div>
<button type="submit">Upload Single File</button>
</form>
<!-- Multiple files upload -->
<form id="multipleUploadForm" enctype="multipart/form-data">
<h2>Multiple Files Upload</h2>
<div class="form-group">
<label for="files">Choose files:</label>
<input type="file" id="files" name="files" multiple required>
</div>
<button type="submit">Upload Multiple Files</button>
</form>
<!-- Image upload with processing -->
<form id="imageUploadForm" enctype="multipart/form-data">
<h2>Image Upload (with processing)</h2>
<div class="form-group">
<label for="image">Choose image:</label>
<input type="file" id="image" name="image" accept="image/*" required>
</div>
<button type="submit">Upload and Process Image</button>
</form>
<div id="result" class="result" style="display: none;"></div>
</div>
<script>
// Single file upload
document.getElementById('singleUploadForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData();
const fileInput = document.getElementById('file');
formData.append('file', fileInput.files[0]);
try {
const response = await fetch('/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
showResult(result);
} catch (error) {
showResult({ error: error.message });
}
});
// Multiple files upload
document.getElementById('multipleUploadForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData();
const fileInput = document.getElementById('files');
for (let file of fileInput.files) {
formData.append('files', file);
}
try {
const response = await fetch('/upload-multiple', {
method: 'POST',
body: formData
});
const result = await response.json();
showResult(result);
} catch (error) {
showResult({ error: error.message });
}
});
// Image upload
document.getElementById('imageUploadForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData();
const fileInput = document.getElementById('image');
formData.append('image', fileInput.files[0]);
try {
const response = await fetch('/upload-image', {
method: 'POST',
body: formData
});
const result = await response.json();
showResult(result);
} catch (error) {
showResult({ error: error.message });
}
});
function showResult(result) {
const resultDiv = document.getElementById('result');
resultDiv.style.display = 'block';
resultDiv.innerHTML = '<pre>' + JSON.stringify(result, null, 2) + '</pre>';
}
</script>
</body>
</html>
Security Best Practices
1. File Type Validation
const allowedTypes = {
'image/jpeg': ['.jpg', '.jpeg'],
'image/png': ['.png'],
'image/gif': ['.gif'],
'application/pdf': ['.pdf'],
'text/plain': ['.txt']
};
function validateFileType(file) {
const ext = path.extname(file.originalname).toLowerCase();
const mimeType = file.mimetype;
if (!allowedTypes[mimeType]) {
return false;
}
return allowedTypes[mimeType].includes(ext);
}
2. File Size Limits
const upload = multer({
storage: storage,
limits: {
fileSize: 5 * 1024 * 1024, // 5MB
files: 10 // Maximum 10 files
}
});
3. Filename Sanitization
const sanitizeFilename = (filename) => {
return filename
.replace(/[^a-zA-Z0-9.-]/g, '_') // Replace special chars
.replace(/_{2,}/g, '_') // Replace multiple underscores
.toLowerCase();
};
const storage = multer.diskStorage({
filename: (req, file, cb) => {
const sanitized = sanitizeFilename(file.originalname);
cb(null, Date.now() + '-' + sanitized);
}
});
4. Virus Scanning
const ClamAV = require('clamav.js');
const scanFile = async (filePath) => {
try {
const result = await ClamAV.scanFile(filePath);
return result.isClean;
} catch (error) {
console.error('Virus scan error:', error);
return false;
}
};
// Use in upload handler
app.post('/upload', upload.single('file'), async (req, res) => {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
const isClean = await scanFile(req.file.path);
if (!isClean) {
// Delete the file
fs.unlinkSync(req.file.path);
return res.status(400).json({ error: 'File failed virus scan' });
}
res.json({ message: 'File uploaded successfully' });
});
Next Steps
Now that you understand file uploads, you're ready to:
- Node.js - Send an Email - Implement email functionality
- Node.js - Events - Learn event-driven programming
- Node.js - Event Loop - Understand the event loop
- Node.js - Event Emitter - Learn custom event handling
File Upload Mastery Complete! You now know how to handle file uploads securely and efficiently in Node.js applications. File uploads are essential for many web applications!