Basic Uploader
Getting a chunked upload working shouldn't take more than a few minutes. This walkthrough covers the minimal Resumable.js setup — instantiation, a file input, a progress bar, success feedback, and error handling — so you have a working foundation before layering on drag-and-drop, retries, or framework integrations. If you're exploring other patterns, the Examples hub has the full catalogue.
HTML Skeleton
Start with a plain HTML page. You need three things visible to the user: a button to choose files, a progress indicator, and a status message area.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Basic Resumable.js Uploader</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 640px; margin: 2rem auto; }
#progress-bar { width: 100%; height: 24px; background: #e5e7eb; border-radius: 4px; overflow: hidden; }
#progress-fill { height: 100%; width: 0%; background: #2563eb; transition: width 0.2s ease; }
#status { margin-top: 1rem; font-size: 0.875rem; color: #374151; }
.btn { padding: 0.5rem 1rem; background: #2563eb; color: #fff; border: none; border-radius: 4px; cursor: pointer; }
</style>
</head>
<body>
<h1>Upload a File</h1>
<button id="browse-btn" class="btn">Choose File</button>
<div id="progress-bar"><div id="progress-fill"></div></div>
<p id="status">No file selected.</p>
<script src="resumable.js"></script>
<script src="uploader.js"></script>
</body>
</html>
Nothing fancy — a styled div acting as the progress bar, a button Resumable.js will bind to, and a paragraph for status text.
Instantiation and Event Wiring
Here's the complete uploader.js. Every line matters, so read the inline comments.
// uploader.js
var r = new Resumable({
target: '/api/upload', // POST endpoint that receives chunks
chunkSize: 1 * 1024 * 1024, // 1 MB per chunk
simultaneousUploads: 3,
testChunks: false, // skip GET pre-check for simplicity
throttleProgressCallbacks: 0.5
});
// Feature detection — Resumable.js needs File API + Blob slicing
if (!r.support) {
document.getElementById('status').textContent =
'Your browser does not support chunked uploads.';
} else {
// Bind the "Choose File" button
r.assignBrowse(document.getElementById('browse-btn'));
// --- Events ---
r.on('fileAdded', function (file, event) {
document.getElementById('status').textContent =
'Added: ' + file.fileName + ' (' + formatBytes(file.size) + ')';
r.upload(); // start immediately
});
r.on('fileProgress', function (file) {
var pct = Math.floor(file.progress() * 100);
document.getElementById('progress-fill').style.width = pct + '%';
document.getElementById('status').textContent = 'Uploading… ' + pct + '%';
});
r.on('fileSuccess', function (file, message) {
document.getElementById('progress-fill').style.width = '100%';
document.getElementById('status').textContent =
'✓ ' + file.fileName + ' uploaded successfully.';
});
r.on('fileError', function (file, message) {
document.getElementById('status').textContent =
'✗ Error uploading ' + file.fileName + ': ' + message;
});
r.on('error', function (message, file) {
console.error('Upload error:', message);
document.getElementById('status').textContent = 'Upload failed: ' + message;
});
}
function formatBytes(bytes) {
if (bytes === 0) return '0 B';
var k = 1024, sizes = ['B', 'KB', 'MB', 'GB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
What's Happening Under the Hood
When the user selects a file, Resumable.js slices it into 1 MB chunks and POSTs each one to /api/upload. Think of it like mailing a book chapter by chapter instead of shipping the whole manuscript — if one envelope gets lost, you resend that chapter, not the entire book.
fileProgress fires on every chunk acknowledgment, giving you a smooth progress bar. fileSuccess fires once the last chunk is confirmed. And fileError catches per-file failures so you can surface meaningful feedback rather than a silent void.
Minimal Server Endpoint
Your server needs to accept multipart POST requests. Here's a bare-bones Node.js handler to prove the concept:
// server.js (Express)
const express = require('express');
const multer = require('multer');
const path = require('path');
const upload = multer({ dest: 'uploads/' });
const app = express();
app.post('/api/upload', upload.single('file'), (req, res) => {
// req.body contains: resumableChunkNumber, resumableTotalChunks, etc.
console.log(`Chunk ${req.body.resumableChunkNumber} of ${req.body.resumableTotalChunks}`);
res.status(200).send('OK');
});
app.listen(3000, () => console.log('Listening on :3000'));
This doesn't reassemble chunks — it just accepts them. For production reassembly strategies, chunk ordering, and cleanup, see the server receivers guide.
Quick Checklist
Before you move on to more advanced patterns, confirm:
- Browser support check is in place (
r.support). - Progress feedback updates on every
fileProgressevent. - Error states surface to the user, not just the console.
chunkSizemakes sense for your expected file sizes and network conditions.- The server returns a
200for each chunk; anything outside the 200 range triggers the error path.
That's a working uploader in under 60 lines of JavaScript. From here you can add drag-and-drop, retry logic, or wrap it in a React component — each covered in the examples collection.
