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.