How to Calculate Axios Upload Speed Dynamically

Calculating real-time upload speed with Axios involves tracking the number of bytes transferred over specific time intervals during the upload process. By leveraging the onUploadProgress configuration option in Axios, you can measure elapsed time alongside the bytes loaded to calculate instantaneous throughput, rolling averages, and remaining time dynamically in both browser and Node.js environments.


Core Formula for Transfer Speed

Transfer speed is defined as data transferred divided by the time taken:

\[\text{Speed (Bytes/sec)} = \frac{\text{Bytes Transferred}}{\text{Time Elapsed (seconds)}}\]

To make this calculation dynamic rather than a static total average, you must measure the difference in bytes and time between consecutive progress events.


Implementation with Axios

Axios provides the onUploadProgress callback in its request configuration. The event object contains loaded (bytes transferred) and total (total file size in bytes).

Here is a complete implementation using dynamic interval tracking:

import axios from 'axios';

async function uploadFileWithSpeedTracking(file, uploadUrl) {
  const formData = new FormData();
  formData.append('file', file);

  let lastTime = performance.now();
  let lastLoaded = 0;

  const response = await axios.post(uploadUrl, formData, {
    headers: {
      'Content-Type': 'multipart/form-data',
    },
    onUploadProgress: (progressEvent) => {
      const currentTime = performance.now();
      const timeElapsedInSeconds = (currentTime - lastTime) / 1000;

      // Avoid division by zero on rapid consecutive events
      if (timeElapsedInSeconds > 0) {
        const bytesChunk = progressEvent.loaded - lastLoaded;
        const currentSpeedBytesPerSec = bytesChunk / timeElapsedInSeconds;

        // Calculate progress percentage
        const percentCompleted = progressEvent.total
          ? Math.round((progressEvent.loaded * 100) / progressEvent.total)
          : 0;

        // Calculate Estimated Time Remaining (ETA)
        const remainingBytes = progressEvent.total - progressEvent.loaded;
        const etaSeconds = currentSpeedBytesPerSec > 0 
          ? (remainingBytes / currentSpeedBytesPerSec).toFixed(1) 
          : 0;

        console.log({
          percent: `${percentCompleted}%`,
          speed: formatSpeed(currentSpeedBytesPerSec),
          eta: `${etaSeconds}s`,
        });

        // Update tracking references for the next event
        lastTime = currentTime;
        lastLoaded = progressEvent.loaded;
      }
    },
  });

  return response.data;
}

function formatSpeed(bytesPerSecond) {
  if (bytesPerSecond >= 1024 * 1024) {
    return `${(bytesPerSecond / (1024 * 1024)).toFixed(2)} MB/s`;
  }
  if (bytesPerSecond >= 1024) {
    return `${(bytesPerSecond / 1024).toFixed(2)} KB/s`;
  }
  return `${Math.round(bytesPerSecond)} B/s`;
}

Improving Accuracy with Moving Averages

Instantaneous measurements can fluctuate rapidly due to network jitter. Implementing an Exponential Moving Average (EMA) provides smoother UI updates:

let smoothedSpeed = 0;
const smoothingFactor = 0.2; // Adjust between 0.1 (smoother) and 0.9 (more responsive)

function updateSmoothedSpeed(instantaneousSpeed) {
  if (smoothedSpeed === 0) {
    smoothedSpeed = instantaneousSpeed;
  } else {
    smoothedSpeed = (instantaneousSpeed * smoothingFactor) + (smoothedSpeed * (1 - smoothingFactor));
  }
  return smoothedSpeed;
}

Integrating this helper smooths out erratic changes when rendering upload speeds on a progress bar or status dashboard.