How to Upload Large Videos in Chunks Using Axios

Uploading large video files over standard HTTP connections often leads to network timeouts, high memory consumption, and failed transfers due to unstable connections. The standard procedure for overcoming these limitations is chunked or multipart uploading, where a large video file is sliced into smaller binary segments on the client side and uploaded sequentially or concurrently using Axios. This article outlines the step-by-step process of preparing file slices, transmitting them with Axios, tracking upload progress, and finalizing the reassembly on the server.

1. Slice the Video File into Chunks

The HTML5 File API allows you to treat a video file as a Blob and slice it into byte ranges. Define a uniform chunk size (typically between 1 MB and 10 MB depending on server configurations) and calculate the total number of segments.

const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB per chunk

function createChunks(file) {
  const chunks = [];
  let currentByte = 0;

  while (currentByte < file.size) {
    const chunk = file.slice(currentByte, currentByte + CHUNK_SIZE);
    chunks.push(chunk);
    currentByte += CHUNK_SIZE;
  }

  return chunks;
}

2. Initialize the Upload Session

Before sending raw binary segments, initiate an upload session with the backend. This request notifies the server of the incoming file name, total size, MIME type, and total chunk count. The server returns a unique uploadId used to group the segments.

async function initUploadSession(file, totalChunks) {
  const response = await axios.post('/api/upload/init', {
    fileName: file.name,
    fileSize: file.size,
    totalChunks: totalChunks
  });
  return response.data.uploadId;
}

3. Upload Segments Sequentially with Axios

Iterate through the array of chunks and send each segment as multipart form data. Include metadata headers or form fields such as the chunk index and the uploadId so the server can track and store each piece accurately.

async function uploadChunks(chunks, uploadId, file) {
  for (let index = 0; index < chunks.length; index++) {
    const formData = new FormData();
    formData.append('chunk', chunks[index]);
    formData.append('chunkIndex', index);
    formData.append('uploadId', uploadId);

    await axios.post('/api/upload/chunk', formData, {
      headers: {
        'Content-Type': 'multipart/form-data',
      },
      onUploadProgress: (progressEvent) => {
        const percentCompleted = Math.round(
          ((index * CHUNK_SIZE + progressEvent.loaded) / file.size) * 100
        );
        console.log(`Upload Progress: ${Math.min(percentCompleted, 100)}%`);
      }
    });
  }
}

4. Implement Retry Logic for Failed Chunks

To ensure resilience, wrap the chunk upload request in a retry mechanism. If a network blip occurs, only the affected chunk is retransmitted rather than restarting the entire video transfer.

async function uploadChunkWithRetry(formData, retries = 3) {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      return await axios.post('/api/upload/chunk', formData, {
        headers: { 'Content-Type': 'multipart/form-data' }
      });
    } catch (error) {
      if (attempt === retries) throw error;
      await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
    }
  }
}

5. Finalize and Merge Chunks

Once all segments have been successfully uploaded, send a completion request containing the uploadId and file details. The backend uses this signal to concatenate the temporary chunk files in order and generate the final video file.

async function completeUpload(uploadId, fileName) {
  const response = await axios.post('/api/upload/complete', {
    uploadId,
    fileName
  });
  return response.data;
}

6. Executing the Complete Workflow

Combine all operations into a single orchestrator function:

async function handleVideoUpload(file) {
  try {
    const chunks = createChunks(file);
    const uploadId = await initUploadSession(file, chunks.length);
    
    await uploadChunks(chunks, uploadId, file);
    
    const result = await completeUpload(uploadId, file.name);
    console.log('Video upload and assembly complete:', result);
  } catch (error) {
    console.error('Upload failed:', error);
  }
}