Understanding File Uploads
File uploads are a crucial part of many web applications, allowing users to share images, documents, videos, and other media. Unlike regular form data or JSON, file uploads require special handling due to their binary nature and potential size.
Common File Upload Use Cases
- User Avatars/Profile Pictures - Personal images representing users
- Product Images - Visual representations for e-commerce
- Document Management - PDFs, spreadsheets, presentations
- Media Sharing - Photos, videos, audio files
- Data Import - CSV, XML, JSON data files
- Backup Files - System backups or exported data
Think of file uploads like sending a package through the mail instead of a simple letter. The package requires special handling, wrapping, labeling, and might need to go through different processing channels due to its size and contents.
Multer: The Express File Upload Middleware
Multer is a popular Node.js middleware specifically designed for handling multipart/form-data, the format used for file uploads in HTML forms. Multer makes it easy to accept, process, and store uploaded files in Express applications.
Key Multer Features
- Handles multipart form data processing
- Provides flexible storage options (memory, disk, custom)
- Supports file filtering by type, size, etc.
- Handles single or multiple file uploads
- Integrates seamlessly with Express.js
- Provides detailed file metadata
Basic Multer Setup
const express = require('express');
const multer = require('multer');
const path = require('path');
const app = express();
// Configure multer storage
const storage = multer.diskStorage({
destination: function(req, file, cb) {
// Set the destination folder for uploads
cb(null, 'uploads/');
},
filename: function(req, file, cb) {
// Set the filename to be unique
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
}
});
// Initialize multer with the storage configuration
const upload = multer({ storage: storage });
// Create an endpoint for single file upload
app.post('/upload', upload.single('file'), (req, res) => {
// req.file contains information about the uploaded file
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
// Return information about the uploaded file
res.json({
message: 'File uploaded successfully',
file: {
filename: req.file.filename,
originalname: req.file.originalname,
mimetype: req.file.mimetype,
size: req.file.size,
path: req.file.path
}
});
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
HTML Form for File Upload
To upload files, your HTML form needs the enctype="multipart/form-data" attribute:
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file" />
<button type="submit">Upload</button>
</form>
Multer Configuration Options
Multer provides numerous configuration options to control how files are handled, stored, and validated.
Storage Options
Multer provides several storage strategies for uploaded files:
Disk Storage
const storage = multer.diskStorage({
// Where to store the files
destination: function(req, file, cb) {
cb(null, 'uploads/'); // Files saved to 'uploads' folder
},
// How to name the files
filename: function(req, file, cb) {
// Customize filename to prevent collisions
const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1E9)}${path.extname(file.originalname)}`;
cb(null, uniqueName);
}
});
Memory Storage
// Store files in memory as Buffer objects
// Useful for small files or when processing without saving
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });
app.post('/process-image', upload.single('image'), (req, res) => {
// File is available as a buffer in req.file.buffer
const imageBuffer = req.file.buffer;
// Process the image (e.g., resize with Sharp)
// processImage(imageBuffer).then(processed => {
// // Handle processed image...
// });
res.json({ message: 'Image processing started' });
});
File Limits and Validation
Control the size and type of files that can be uploaded:
Setting File Limits
const upload = multer({
storage: multer.diskStorage({/* ... */}),
limits: {
fileSize: 5 * 1024 * 1024, // 5MB in bytes
files: 5 // Max number of files
},
fileFilter: function(req, file, cb) {
// Check file type
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (!allowedTypes.includes(file.mimetype)) {
// Reject file
return cb(new Error('Only JPEG, PNG, and GIF images are allowed'), false);
}
// Accept file
cb(null, true);
}
});
Upload File Types
Multer provides several methods to handle different file upload scenarios:
Single File Upload
// Handling a single file upload from a field named 'avatar'
app.post('/profile', upload.single('avatar'), (req, res) => {
// req.file contains information about the uploaded file
console.log(req.file);
/* Example output:
{
fieldname: 'avatar',
originalname: 'profile.jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
destination: 'uploads/',
filename: '1620312345678-293847501.jpg',
path: 'uploads/1620312345678-293847501.jpg',
size: 58243
}
*/
// req.body contains text fields, if any
console.log(req.body);
res.send('Profile updated!');
});
Multiple Files Upload
// Handling multiple files upload from a field named 'photos'
app.post('/gallery', upload.array('photos', 5), (req, res) => {
// req.files contains an array of files
console.log(`Received ${req.files.length} files`);
// Map files to a more user-friendly format
const uploadedFiles = req.files.map(file => ({
name: file.originalname,
type: file.mimetype,
size: file.size,
path: file.path
}));
res.json({
message: 'Files uploaded successfully!',
files: uploadedFiles
});
});
Multiple Fields Upload
// Handling multiple fields with different files
const uploadFields = upload.fields([
{ name: 'avatar', maxCount: 1 },
{ name: 'gallery', maxCount: 5 }
]);
app.post('/profile-complete', uploadFields, (req, res) => {
// req.files is an object where key is the field name
console.log(req.files);
/* Example output:
{
avatar: [
{
fieldname: 'avatar',
originalname: 'profile.jpg',
// ... other file properties
}
],
gallery: [
{
fieldname: 'gallery',
originalname: 'vacation1.jpg',
// ... other file properties
},
{
fieldname: 'gallery',
originalname: 'vacation2.jpg',
// ... other file properties
}
// ... more gallery files
]
}
*/
res.send('Profile updated with gallery!');
});
Error Handling with Multer
Proper error handling is crucial for a good user experience when processing file uploads.
Handling Multer Errors
// Create multer instance with limits and filters
const upload = multer({
storage: multer.diskStorage({/* ... */}),
limits: {
fileSize: 2 * 1024 * 1024 // 2MB
},
fileFilter: function(req, file, cb) {
// Only accept image files
if (!file.originalname.match(/\.(jpg|jpeg|png|gif)$/)) {
return cb(new Error('Only image files are allowed!'), false);
}
cb(null, true);
}
});
// Custom error handling middleware
function handleMulterError(err, req, res, next) {
if (err instanceof multer.MulterError) {
// A Multer error occurred during upload
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(413).json({
error: 'File too large',
message: 'File size exceeds the 2MB limit',
code: 'FILE_TOO_LARGE'
});
} else if (err.code === 'LIMIT_FILE_COUNT') {
return res.status(400).json({
error: 'Too many files',
message: 'You can only upload up to 5 files at once',
code: 'TOO_MANY_FILES'
});
} else {
// Other Multer errors
return res.status(400).json({
error: 'Upload error',
message: err.message,
code: err.code
});
}
} else if (err) {
// Non-Multer error (e.g., from fileFilter)
return res.status(400).json({
error: 'File validation error',
message: err.message || 'Unknown error during file upload'
});
}
// No error, proceed to the next middleware
next();
}
// Use the upload middleware with error handling
app.post('/upload', function(req, res, next) {
// Use upload.single as middleware, and catch errors
upload.single('file')(req, res, function(err) {
// Pass any errors to our handler
if (err) {
return handleMulterError(err, req, res, next);
}
// No errors, process the uploaded file
if (!req.file) {
return res.status(400).json({ error: 'No file provided' });
}
res.json({
message: 'File uploaded successfully',
file: req.file.filename
});
});
});
Common Multer Error Codes
LIMIT_PART_COUNT: Too many parts in the multipart formLIMIT_FILE_SIZE: File size exceeds the configured limitLIMIT_FILE_COUNT: Too many files uploadedLIMIT_FIELD_KEY: Field name too longLIMIT_FIELD_VALUE: Field value too longLIMIT_FIELD_COUNT: Too many fieldsLIMIT_UNEXPECTED_FILE: Unexpected field name
Advanced File Handling
File Type Validation
It's important to validate file types beyond just checking file extensions, as they can be easily manipulated:
Advanced File Type Validation
const fs = require('fs');
const FileType = require('file-type');
// File filter that checks actual file contents
const fileFilter = async (req, file, cb) => {
try {
// Check mimetype first (basic check)
const allowedMimes = ['image/jpeg', 'image/png', 'image/gif'];
if (!allowedMimes.includes(file.mimetype)) {
return cb(new Error('File type not allowed'), false);
}
// For more security, we could analyze the file buffer
// This requires storing the file first, or using memoryStorage
// Example with memoryStorage:
/*
// Read the first few bytes to determine file type
const buffer = file.buffer;
const fileTypeResult = await FileType.fromBuffer(buffer);
if (!fileTypeResult || !allowedMimes.includes(fileTypeResult.mime)) {
return cb(new Error('Invalid file content type'), false);
}
*/
// File passed validation
cb(null, true);
} catch (err) {
cb(new Error('Error validating file type'), false);
}
};
const upload = multer({
storage: multer.diskStorage({/* ... */}),
fileFilter: fileFilter
});
Handling File Processing
Often, you'll need to process uploaded files before storing them permanently:
Image Processing with Sharp
const express = require('express');
const multer = require('multer');
const sharp = require('sharp');
const path = require('path');
const fs = require('fs');
const app = express();
// Use memory storage for processing before saving
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
fileFilter: (req, file, cb) => {
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new Error('Only images are allowed'), false);
}
}
});
app.post('/upload-profile', upload.single('avatar'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No image uploaded' });
}
// Get uploaded file buffer
const imageBuffer = req.file.buffer;
const filename = Date.now() + '.webp';
const outputPath = path.join('uploads', filename);
// Process the image: resize to 300x300, convert to WebP format
await sharp(imageBuffer)
.resize(300, 300, {
fit: 'cover',
position: 'center'
})
.webp({ quality: 80 })
.toFile(outputPath);
// Generate a thumbnail version as well
await sharp(imageBuffer)
.resize(100, 100, {
fit: 'cover',
position: 'center'
})
.webp({ quality: 70 })
.toFile(path.join('uploads', 'thumb_' + filename));
// Return success with file info
res.json({
message: 'Profile image uploaded and processed',
image: {
url: `/images/${filename}`,
thumbnail: `/images/thumb_${filename}`
}
});
} catch (error) {
console.error('Error processing image:', error);
res.status(500).json({
error: 'Failed to process image',
details: error.message
});
}
});
Storing Upload Metadata
In real applications, you'll likely store file metadata in a database:
Storing File Metadata in Database
// Pseudo-code example with a fictional database client
const express = require('express');
const multer = require('multer');
const db = require('./database'); // Your database client
const app = express();
const storage = multer.diskStorage({/* ... */});
const upload = multer({ storage });
app.post('/documents', upload.single('document'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No document uploaded' });
}
// Get the uploaded file info
const { originalname, mimetype, filename, size, path } = req.file;
// Get additional metadata from the request body
const { title, description, category } = req.body;
// Store metadata in database
const fileId = await db.files.create({
originalName: originalname,
storedName: filename,
mimeType: mimetype,
size: size,
path: path,
title: title || originalname,
description: description || '',
category: category || 'uncategorized',
uploadedBy: req.user.id, // Assuming authenticated user
uploadedAt: new Date(),
status: 'active'
});
// Return success response with file ID
res.status(201).json({
message: 'Document uploaded successfully',
fileId: fileId,
title: title || originalname,
url: `/documents/${fileId}`
});
} catch (error) {
console.error('Error storing document metadata:', error);
res.status(500).json({
error: 'Failed to complete document upload',
details: error.message
});
}
});
Cloud Storage Integration
For production applications, storing files in cloud services like AWS S3, Google Cloud Storage, or Azure Blob Storage is often preferred over local storage.
Multer with AWS S3 Storage
const express = require('express');
const multer = require('multer');
const multerS3 = require('multer-s3');
const { S3Client } = require('@aws-sdk/client-s3');
const app = express();
// Configure AWS S3 client
const s3 = new S3Client({
region: 'us-east-1',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
}
});
// Configure multer to use S3 storage
const upload = multer({
storage: multerS3({
s3: s3,
bucket: 'my-app-uploads',
acl: 'public-read', // Make files public
contentType: multerS3.AUTO_CONTENT_TYPE, // Set content type based on file
metadata: function (req, file, cb) {
// Set custom metadata
cb(null, {
fieldName: file.fieldname,
uploadedBy: req.user ? req.user.id : 'anonymous'
});
},
key: function (req, file, cb) {
// Set file path and name in S3
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const key = `uploads/${req.user ? req.user.id : 'public'}/${uniqueSuffix}-${file.originalname}`;
cb(null, key);
}
}),
limits: { fileSize: 10 * 1024 * 1024 } // 10MB
});
app.post('/upload-to-s3', upload.single('file'), (req, res) => {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
// File upload success, file information in req.file
res.json({
message: 'File uploaded to S3 successfully',
file: {
url: req.file.location, // S3 URL to the file
key: req.file.key,
size: req.file.size,
mimetype: req.file.mimetype
}
});
});
Using cloud storage provides several benefits:
- Scalability - Handle large numbers of uploads without server storage concerns
- Reliability - Built-in redundancy and availability
- CDN Integration - Fast global file delivery
- Security Features - Access controls, encryption, etc.
- Server Independence - Files available even if your server is down
Security Considerations
File uploads present several security challenges that need to be addressed:
Security Best Practices Implementation
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const { promisify } = require('util');
const fileTypeFromBuffer = require('file-type').fromBuffer;
const app = express();
// Create upload directory if it doesn't exist
const uploadDir = path.join(__dirname, 'uploads');
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
// Sanitize filename to prevent path traversal
function sanitizeFilename(filename) {
// Remove path components
const sanitized = path.basename(filename);
// Remove dangerous characters
return sanitized.replace(/[^a-zA-Z0-9_.-]/g, '_');
}
// Generate a secure random filename
async function generateSecureFilename(originalname) {
const randomBytes = await promisify(crypto.randomBytes)(16);
const extension = path.extname(originalname).toLowerCase();
return `${randomBytes.toString('hex')}${extension}`;
}
// Configure storage
const storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, uploadDir);
},
filename: async function(req, file, cb) {
try {
const secureName = await generateSecureFilename(file.originalname);
cb(null, secureName);
} catch (err) {
cb(err);
}
}
});
// Configure file filter
const fileFilter = async function(req, file, cb) {
try {
// Whitelist of allowed MIME types
const allowedMimes = ['image/jpeg', 'image/png', 'application/pdf'];
if (!allowedMimes.includes(file.mimetype)) {
return cb(new Error('File type not allowed'), false);
}
// Additional validation could be performed here
// For example, if using memoryStorage, check the actual file contents
cb(null, true);
} catch (err) {
cb(err);
}
};
// Configure limits
const limits = {
fileSize: 5 * 1024 * 1024, // 5MB
files: 1 // Only one file at a time
};
const upload = multer({
storage: storage,
fileFilter: fileFilter,
limits: limits
});
// Secure upload endpoint
app.post('/secure-upload', async (req, res) => {
try {
// Handle the upload with custom error handling
upload.single('file')(req, res, async function(err) {
if (err) {
console.error('Upload error:', err);
return res.status(400).json({
error: err.code || 'UPLOAD_ERROR',
message: err.message || 'Error uploading file'
});
}
// Check if file was uploaded
if (!req.file) {
return res.status(400).json({
error: 'NO_FILE',
message: 'No file was uploaded'
});
}
// Verify file content type for extra security (if using memoryStorage)
// if (req.file.buffer) {
// const fileInfo = await fileTypeFromBuffer(req.file.buffer);
// if (!fileInfo || !allowedMimes.includes(fileInfo.mime)) {
// // Delete the file if its content doesn't match allowed types
// fs.unlinkSync(req.file.path);
// return res.status(400).json({
// error: 'INVALID_CONTENT',
// message: 'File content type not allowed'
// });
// }
// }
// Set permissions on the uploaded file
fs.chmodSync(req.file.path, 0o640); // Owner: read/write, Group: read, Others: none
// Log the upload (in production, store in database)
console.log(`File uploaded: ${req.file.originalname} → ${req.file.filename}`);
// Return success with limited file info (don't expose full paths)
res.json({
success: true,
file: {
name: req.file.originalname,
size: req.file.size,
type: req.file.mimetype,
// Use a URL that doesn't expose the physical path
url: `/files/${req.file.filename}`
}
});
});
} catch (error) {
console.error('Unexpected error:', error);
res.status(500).json({
error: 'SERVER_ERROR',
message: 'An unexpected error occurred'
});
}
});
// Serve uploaded files securely
app.get('/files/:filename', (req, res) => {
const filename = sanitizeFilename(req.params.filename);
const filePath = path.join(uploadDir, filename);
// Check if file exists
if (!fs.existsSync(filePath)) {
return res.status(404).json({ error: 'File not found' });
}
// Optional: Check user authorization to access this file
// if (!userCanAccessFile(req.user, filename)) {
// return res.status(403).json({ error: 'Access denied' });
// }
// Set proper headers and send file
res.sendFile(filePath);
});
Key Security Recommendations
- Validate File Types - Check both MIME types and file contents
- Enforce Size Limits - Prevent server overload and DoS attacks
- Use Secure Filenames - Randomize names and prevent path traversal
- Store Files Securely - Outside of web root with proper permissions
- Scan for Malware - Use virus scanning services in production
- Implement Access Controls - Ensure only authorized users can access uploaded files
- Consider Cloud Storage - Let specialized services handle security
Real-World Application: Media Upload Service
Let's build a more comprehensive example of a media upload service that handles different types of files:
Complete Media Upload Service
const express = require('express');
const multer = require('multer');
const sharp = require('sharp');
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const { promisify } = require('util');
const app = express();
app.use(express.json());
// Create required directories
const baseUploadDir = path.join(__dirname, 'uploads');
const imageDir = path.join(baseUploadDir, 'images');
const documentDir = path.join(baseUploadDir, 'documents');
const videoDir = path.join(baseUploadDir, 'videos');
[baseUploadDir, imageDir, documentDir, videoDir].forEach(dir => {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
});
// Generate a secure random filename
async function generateSecureFilename(originalname) {
const randomBytes = await promisify(crypto.randomBytes)(16);
const extension = path.extname(originalname).toLowerCase();
return `${randomBytes.toString('hex')}${extension}`;
}
// Configure storage strategy based on file type
const storage = multer.diskStorage({
destination: function(req, file, cb) {
let uploadDir;
if (file.mimetype.startsWith('image/')) {
uploadDir = imageDir;
} else if (file.mimetype.startsWith('video/')) {
uploadDir = videoDir;
} else {
uploadDir = documentDir;
}
cb(null, uploadDir);
},
filename: async function(req, file, cb) {
try {
const secureName = await generateSecureFilename(file.originalname);
cb(null, secureName);
} catch (err) {
cb(err);
}
}
});
// Configure file filter based on allowed types
const fileFilter = function(req, file, cb) {
// Define allowed MIME types
const allowedTypes = {
image: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
document: ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'text/plain', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
video: ['video/mp4', 'video/quicktime', 'video/x-msvideo']
};
// Check upload type from request
const uploadType = req.params.type || req.query.type || 'image';
// Get allowed MIME types for this upload type
const allowed = allowedTypes[uploadType] || allowedTypes.image;
if (!allowed.includes(file.mimetype)) {
return cb(new Error(`File type not allowed. Allowed types: ${allowed.join(', ')}`), false);
}
cb(null, true);
};
// Set limits based on file type
function getLimits(uploadType) {
const limits = {
image: { fileSize: 5 * 1024 * 1024 }, // 5MB
document: { fileSize: 10 * 1024 * 1024 }, // 10MB
video: { fileSize: 100 * 1024 * 1024 } // 100MB
};
return limits[uploadType] || limits.image;
}
// Create upload middleware function based on request type
function createUploadMiddleware(req, res, next) {
const uploadType = req.params.type || req.query.type || 'image';
const upload = multer({
storage: storage,
fileFilter: fileFilter,
limits: getLimits(uploadType)
});
return upload.single('file');
}
// Media upload endpoint with dynamic type
app.post('/upload/:type?', (req, res, next) => {
const uploadMiddleware = createUploadMiddleware(req, res, next);
uploadMiddleware(req, res, async function(err) {
if (err) {
console.error('Upload error:', err);
return res.status(400).json({
success: false,
error: err.code || 'UPLOAD_ERROR',
message: err.message || 'Error uploading file'
});
}
if (!req.file) {
return res.status(400).json({
success: false,
error: 'NO_FILE',
message: 'No file was uploaded'
});
}
try {
// Get upload type
const uploadType = req.params.type || req.query.type || 'image';
// Process based on file type
if (uploadType === 'image' && req.file.mimetype.startsWith('image/')) {
// Process image (resize, create thumbnails, etc.)
await processImage(req.file);
} else if (uploadType === 'document') {
// Process document (e.g., generate preview)
// processDocument(req.file);
} else if (uploadType === 'video') {
// Process video (e.g., generate thumbnail)
// processVideo(req.file);
}
// Create media record
const media = {
id: crypto.randomBytes(8).toString('hex'),
originalName: req.file.originalname,
fileName: req.file.filename,
fileSize: req.file.size,
mimeType: req.file.mimetype,
fileType: uploadType,
uploadedAt: new Date(),
path: req.file.path,
url: `/media/${uploadType}/${req.file.filename}`
};
// Store media record (in a real app, this would go to a database)
// await mediaService.create(media);
// Respond with success
res.status(201).json({
success: true,
message: 'File uploaded successfully',
media: {
id: media.id,
name: media.originalName,
type: media.fileType,
size: media.fileSize,
url: media.url,
uploadedAt: media.uploadedAt
}
});
} catch (error) {
console.error('Processing error:', error);
res.status(500).json({
success: false,
error: 'PROCESSING_ERROR',
message: 'Error processing uploaded file'
});
}
});
});
// Image processing function
async function processImage(file) {
try {
// Only process images
if (!file.mimetype.startsWith('image/')) {
return;
}
const filename = path.basename(file.filename, path.extname(file.filename));
const outputDir = path.dirname(file.path);
// Create thumbnail version
await sharp(file.path)
.resize(200, 200, { fit: 'cover' })
.jpeg({ quality: 80 })
.toFile(path.join(outputDir, `${filename}_thumb.jpg`));
// Create medium version
await sharp(file.path)
.resize(800, 800, { fit: 'inside', withoutEnlargement: true })
.jpeg({ quality: 85 })
.toFile(path.join(outputDir, `${filename}_medium.jpg`));
console.log('Image processed successfully');
} catch (error) {
console.error('Error processing image:', error);
throw error;
}
}
// Serve media files
app.get('/media/:type/:filename', (req, res) => {
const { type, filename } = req.params;
// Validate type and filename to prevent path traversal
const validTypes = ['images', 'documents', 'videos'];
if (!validTypes.includes(type)) {
return res.status(400).json({ error: 'Invalid media type' });
}
const sanitizedFilename = path.basename(filename);
const filePath = path.join(baseUploadDir, type, sanitizedFilename);
// Check if file exists
if (!fs.existsSync(filePath)) {
return res.status(404).json({ error: 'File not found' });
}
// Send file with appropriate headers
res.sendFile(filePath);
});
app.listen(3000, () => {
console.log('Media upload service running on port 3000');
});
Practical Exercises
Exercise 1: Basic File Upload
Create a simple Express application that:
- Accepts image uploads (only JPEG and PNG)
- Stores files in an 'uploads' folder with unique names
- Limits file size to 2MB
- Returns file information after upload
- Handles and displays appropriate error messages
Include a simple HTML form for testing the upload functionality.
Exercise 2: Advanced Media Library
Create an Express application that implements a media library with the following features:
- Handles different file types (images, documents, videos)
- Processes images to create multiple sizes (original, medium, thumbnail)
- Stores file metadata in a JSON file (simulating a database)
- Provides endpoints to:
- Upload files
- List all uploaded files
- Get file details by ID
- Delete files
- Implements proper security measures
Bonus: Add a simple front-end interface for the media library.