Illustration for Basic Uploader example

Basic Uploader

Minimal Resumable.js setup with progress display, error handling, and file completion feedback.

Examples·Updated 2025-10-18

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 fileProgress event.
  • Error states surface to the user, not just the console.
  • chunkSize makes sense for your expected file sizes and network conditions.
  • The server returns a 200 for 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.