Implementing Resumable File Uploads with Axios

Installation

First, add Axios to your project using npm:

npm install axios

Core Implementation

The following example demonstrates a complete resumable upload system with all essential features:

import axios from 'axios';

// Create a cancellation token source
const CancelToken = axios.CancelToken;
let cancelSource = null;

// Initialize chunk size (1MB default)
const DEFAULT_CHUNK_SIZE = 1024 * 1024;

// Continue an interrupted upload
const continueUpload = async (sourceFile, segmentSize, uploadId, uploadUrl) => {
  if (!cancelSource) {
    cancelSource = CancelToken.source();
  }

  // Calculate offset based on previously uploaded bytes
  const offset = Math.min(segmentSize, sourceFile.size);
  
  // Create Blob for current segment
  const blobSlice = sourceFile.slice(0, offset);
  
  // Prepare form data
  const payload = new FormData();
  payload.append('data', blobSlice);
  payload.append('identifier', uploadId);
  payload.append('offset', offset);

  try {
    const response = await axios.post(uploadUrl, payload, {
      headers: { 'Content-Type': 'multipart/form-data' },
      cancelToken: cancelSource.token,
      onUploadProgress: (progressEvent) => {
        const percentCompleted = Math.round(
          (progressEvent.loaded * 100) / progressEvent.total
        );
        console.log(`Upload progress: ${percentCompleted}%`);
      }
    });
    return response.data;
  } catch (error) {
    if (axios.isCancel(error)) {
      console.log('Upload cancelled');
    }
    throw error;
  }
};

// Retry failed upload
const attemptUpload = async (sourceFile, segmentSize, uploadId, uploadUrl) => {
  const maxRetries = 3;
  let attempts = 0;

  while (attempts < maxRetries) {
    try {
      return await continueUpload(sourceFile, segmentSize, uploadId, uploadUrl);
    } catch (error) {
      attempts++;
      console.log(`Retry attempt ${attempts}/${maxRetries}`);
      if (attempts >= maxRetries) {
        throw new Error('Upload failed after multiple attempts');
      }
    }
  }
};

// Pause ongoing upload
const pauseUpload = () => {
  if (cancelSource) {
    cancelSource.cancel('User requested pause');
    cancelSource = null;
  }
};

// Start new upload
const startUpload = async (sourceFile, segmentSize, uploadUrl) => {
  if (!cancelSource) {
    cancelSource = CancelToken.source();
  }

  // Generate unique identifier
  const uniqueId = `upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  
  const payload = new FormData();
  payload.append('data', sourceFile.slice(0, segmentSize));
  payload.append('identifier', uniqueId);
  payload.append('offset', 0);
  payload.append('totalSize', sourceFile.size);

  const response = await axios.post(uploadUrl, payload, {
    headers: { 'Content-Type': 'multipart/form-data' },
    cancelToken: cancelSource.token
  });

  return { uploadId: uniqueId, ...response.data };
};

// Progress tracking utility
const trackProgress = (progressEvent) => {
  const calculation = (progressEvent.loaded / progressEvent.total) * 100;
  return Math.round(calculation);
};

// Resumable upload handler
class ResumableUploadManager {
  constructor(url, options = {}) {
    this.uploadUrl = url;
    this.segmentSize = options.segmentSize || DEFAULT_CHUNK_SIZE;
    this.fileId = null;
  }

  async uploadFile(sourceFile, onProgress) {
    // Start initial upload
    const initialResult = await startUpload(sourceFile, this.segmentSize, this.uploadUrl);
    this.fileId = initialResult.uploadId;

    // Continue with remaining segments
    let currentOffset = this.segmentSize;
    
    while (currentOffset < sourceFile.size) {
      const segment = sourceFile.slice(
        currentOffset,
        currentOffset + this.segmentSize
      );

      try {
        await continueUpload(
          segment,
          this.segmentSize,
          this.fileId,
          this.uploadUrl
        );
        
        if (onProgress) {
          const progress = trackProgress({
            loaded: currentOffset,
            total: sourceFile.size
          });
          onProgress(progress);
        }
        
        currentOffset += this.segmentSize;
      } catch (error) {
        await attemptUpload(
          segment,
          this.segmentSize,
          this.fileId,
          this.uploadUrl
        );
      }
    }

    return { success: true, fileId: this.fileId };
  }
}

Usage Example

// DOM elements
const fileInput = document.getElementById('file-selector');
const progressBar = document.getElementById('upload-progress');
const pauseBtn = document.getElementById('pause-btn');
const resumeBtn = document.getElementById('resume-btn');

// Configuration
const CHUNK_SIZE = 1024 * 1024; // 1MB chunks
const API_ENDPOINT = 'https://api.example.com/files/upload';

// Initialize manager
const uploadManager = new ResumableUploadManager(API_ENDPOINT, {
  segmentSize: CHUNK_SIZE
});

// Handle file selection
fileInput.addEventListener('change', async (event) => {
  const selectedFile = event.target.files[0];
  
  try {
    const result = await uploadManager.uploadFile(selectedFile, (progress) => {
      progressBar.style.width = `${progress}%`;
      progressBar.textContent = `${progress}%`;
    });
    
    console.log('Upload complete:', result);
  } catch (error) {
    console.error('Upload failed:', error.message);
  }
});

// Pause button handler
pauseBtn.addEventListener('click', () => {
  pauseUpload();
});

// Resume button handler
resumeBtn.addEventListener('click', async () => {
  const selectedFile = fileInput.files[0];
  if (selectedFile && uploadManager.fileId) {
    await uploadManager.uploadFile(selectedFile);
  }
});

Key Features Breakdown

Resume Capability: The system maintains upload state using unique identifiers, allowing uploads to continue from the exacct byte position where they stopped.

Retry Logic: The attemptUpload function implements exponential backoff with configurable retry attempts for handling transient network failures.

Progress Tracking: Both onUploadProgress callback in Axios and custom progress calculation provide real-time upload status updates.

Cancellation Control: Using Axios CancelToken allows graceful pausing and cancellation of in-progress uploads without leaving dangling server connections.

Tags: javascript axios File Upload HTTP web development

Posted on Tue, 16 Jun 2026 17:45:32 +0000 by misterfine