Chapter 8: File Upload and Download

Haiyue
7min

Chapter 8: File Upload and Download

Learning Objectives

  1. Master implementing single file upload
  2. Learn to handle multiple file uploads
  3. Implement upload progress monitoring
  4. Master file download functionality
  5. Handle large files and resumable uploads

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 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 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();

    // Cleanup
    document.body.removeChild(link);
    window.URL.revokeObjectURL(downloadUrl);

    return true;
  } catch (error) {
    console.error('File download failed:', error);
    throw error;
  }
}

Large File Chunked Download

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

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 improves user experience
  • File downloads support progress tracking and chunked processing
  • File validation ensures security and compliance

Key Points

  • FormData automatically sets correct Content-Type
  • Progress callbacks provide rich upload/download information
  • Chunking technology handles large file transfers
  • Client-side validation improves user experience, server-side validation ensures security