Multi-Part Chunked File Uploads in Axios

Handling multi-part chunked uploads in Axios involves splitting a large file into smaller binary pieces (chunks) on the client side and transmitting them sequentially or in parallel to a server. This approach prevents network timeout errors, reduces browser memory overhead, and allows users to pause, resume, or retry failed portions of an upload without restarting the entire process from the beginning.

Why Use Chunked Uploads?

Standard HTTP file uploads send the entire file payload in a single POST or PUT request. For files exceeding tens or hundreds of megabytes, this can lead to:

  • High failure rates over unstable connections.
  • Server-side payload size limits (413 Payload Too Large).
  • Poor user experience due to the inability to resume interrupted uploads.

By dividing the file into discrete chunks, you can transmit smaller, manageable payloads and reconstruct the original file on the server once all parts are received.


Step-by-Step Implementation

1. Slice the File

In modern browsers, the standard File object inherits from Blob, which provides the .slice(start, end) method. Use this to divide the file into a specific chunk size (for example, 5 MB).

2. Append Chunks to FormData

Wrap each chunk in a FormData object along with necessary metadata, such as the chunk index, total chunk count, and a unique file identifier.

3. Send Chunks Sequentially with Axios

Iterate through the chunks, sending individual POST requests. Sending chunks sequentially ensures order and makes error handling straightforward.

import axios from 'axios';

async function uploadFileInChunks(file) {
  const CHUNK_SIZE = 5 * 1024 * 1024; // 5 MB per chunk
  const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
  const fileId = `${file.name}-${file.size}-${file.lastModified}`;

  for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
    const start = chunkIndex * CHUNK_SIZE;
    const end = Math.min(start + CHUNK_SIZE, file.size);
    const chunk = file.slice(start, end);

    const formData = new FormData();
    formData.append('file', chunk);
    formData.append('chunkIndex', chunkIndex);
    formData.append('totalChunks', totalChunks);
    formData.append('fileId', fileId);
    formData.append('fileName', file.name);

    try {
      await axios.post('/api/upload-chunk', formData, {
        headers: {
          'Content-Type': 'multipart/form-data',
        },
        onUploadProgress: (progressEvent) => {
          const chunkProgress = progressEvent.loaded / progressEvent.total;
          const totalProgress = ((chunkIndex + chunkProgress) / totalChunks) * 100;
          console.log(`Upload Progress: ${totalProgress.toFixed(2)}%`);
        },
      });
    } catch (error) {
      console.error(`Failed to upload chunk ${chunkIndex}:`, error);
      throw error;
    }
  }

  // Notify the server that all chunks have been uploaded
  await axios.post('/api/finalize-upload', { fileId, fileName: file.name, totalChunks });
  console.log('Upload complete');
}

Best Practices

  • Retry Failed Chunks: Implement an exponential backoff retry loop for individual chunk requests so temporary network drops do not invalidate the entire upload.
  • Concurrency Control: While sequential uploads are simplest, uploading 2–4 chunks concurrently using Promise.all() or a queue can maximize throughput without overloading network resources.
  • File Hashing: Calculate a checksum (e.g., MD5 or SHA-256) of each chunk or the entire file to verify data integrity upon receipt on the server.
  • Cleanup Strategy: Ensure the server has a scheduled cleanup mechanism to delete abandoned or incomplete chunk uploads older than a specific retention window.