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:

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