Chapter 8: File Upload and Download
9/1/25About 2 min
Chapter 8: File Upload and Download
Learning Objectives
- Master the implementation of single file uploads
- Learn to handle multiple file uploads
- Implement upload progress monitoring
- Master file download functionality
- Handle large files and resumable downloads
8.1 File Upload Basics
Single File Upload
// Single file upload
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
try {
const response = await axios.post('/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`Upload progress: ${percent}%`);
}
});
return response.data;
} catch (error) {
console.error('File upload failed:', error);
throw error;
}
}
Multiple File Upload
// Multiple file upload
async function uploadMultipleFiles(files) {
const formData = new FormData();
// Add multiple files
files.forEach((file, index) => {
formData.append(`file${index}`, file);
});
const response = await axios.post('/api/upload/multiple', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`Total upload progress: ${percent}%`);
}
});
return response.data;
}
8.2 Upload Progress Monitoring
Progress Callback Handling
class UploadProgressHandler {
constructor(onProgress) {
this.onProgress = onProgress;
this.startTime = null;
}
handleProgress(progressEvent) {
if (!this.startTime) {
this.startTime = Date.now();
}
const { loaded, total } = progressEvent;
const percent = Math.round((loaded * 100) / total);
const elapsed = Date.now() - this.startTime;
const rate = loaded / elapsed * 1000; // bytes per second
const remaining = (total - loaded) / rate;
this.onProgress({
percent,
loaded,
total,
rate: this.formatBytes(rate) + '/s',
remaining: this.formatTime(remaining)
});
}
formatBytes(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
formatTime(seconds) {
if (seconds < 60) return Math.round(seconds) + 's';
return Math.round(seconds / 60) + 'm';
}
}
8.3 File Download
Basic File Download
// Download a file
async function downloadFile(url, filename) {
try {
const response = await axios({
method: 'GET',
url: url,
responseType: 'blob',
onDownloadProgress: (progressEvent) => {
if (progressEvent.lengthComputable) {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`Download progress: ${percent}%`);
}
}
});
// Create a download link
const blob = new Blob([response.data]);
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
// Clean up
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
return true;
} catch (error) {
console.error('File download failed:', error);
throw error;
}
}
Chunked Download for Large Files
class ChunkedDownloader {
constructor(url, chunkSize = 1024 * 1024) { // 1MB chunks
this.url = url;
this.chunkSize = chunkSize;
}
async download() {
// Get file size
const headResponse = await axios.head(this.url);
const fileSize = parseInt(headResponse.headers['content-length']);
const chunks = [];
const promises = [];
for (let start = 0; start < fileSize; start += this.chunkSize) {
const end = Math.min(start + this.chunkSize - 1, fileSize - 1);
const promise = axios.get(this.url, {
headers: {
Range: `bytes=${start}-${end}`
},
responseType: 'arraybuffer'
}).then(response => {
chunks[start / this.chunkSize] = response.data;
});
promises.push(promise);
}
await Promise.all(promises);
// Merge chunks
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.byteLength, 0);
const result = new Uint8Array(totalLength);
let offset = 0;
chunks.forEach(chunk => {
result.set(new Uint8Array(chunk), offset);
offset += chunk.byteLength;
});
return result.buffer;
}
}
8.4 File Type Validation
Pre-upload Validation
class FileValidator {
constructor() {
this.allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
this.maxSize = 10 * 1024 * 1024; // 10MB
}
validate(file) {
const errors = [];
// Check file type
if (!this.allowedTypes.includes(file.type)) {
errors.push(`Unsupported file type: ${file.type}`);
}
// Check file size
if (file.size > this.maxSize) {
errors.push(`File size exceeds limit: ${this.formatBytes(file.size)}`);
}
// Check filename
if (!this.isValidFilename(file.name)) {
errors.push('Filename contains invalid characters');
}
return {
isValid: errors.length === 0,
errors
};
}
isValidFilename(filename) {
// Check if filename contains invalid characters
const invalidChars = /[<>:"/\|?*]/;
return !invalidChars.test(filename);
}
formatBytes(bytes) {
const sizes = ['B', 'KB', 'MB', 'GB'];
if (bytes === 0) return '0 B';
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
}
}
Chapter Summary
- File uploads require using FormData and multipart/form-data
- Progress monitoring enhances user experience
- File downloads support progress tracking and chunking
- File validation ensures security and compliance
Key Takeaways
- FormData automatically sets the correct Content-Type
- Progress callbacks provide rich upload/download information
- Chunking technology handles large file transfers
- Client-side validation improves user experience, while server-side validation ensures security