Docs

18.4-Blob-File-APIs

18.4 Blob and File APIs

Overview

The Blob and File APIs provide powerful mechanisms for handling binary data and files in JavaScript. Blobs (Binary Large Objects) represent raw data, while File objects extend Blob with file-specific metadata.

Learning Objectives

  • Create and manipulate Blob objects
  • Work with File objects and FileReader
  • Handle file uploads and downloads
  • Convert between data formats
  • Process files in the browser

Core Concepts

Blob Basics

// Create a Blob from text
const textBlob = new Blob(['Hello, World!'], { type: 'text/plain' });

// Create a Blob from JSON
const jsonBlob = new Blob([JSON.stringify({ name: 'John', age: 30 })], {
  type: 'application/json',
});

// Create a Blob from multiple parts
const multiPartBlob = new Blob(['Part 1\n', 'Part 2\n', 'Part 3'], {
  type: 'text/plain',
});

// Blob properties
console.log(textBlob.size); // Size in bytes
console.log(textBlob.type); // MIME type

File Object

// File extends Blob with name and metadata
// Files are typically obtained from input elements or drag & drop

// File from input
const input = document.querySelector('input[type="file"]');
input.addEventListener('change', (e) => {
  const file = e.target.files[0];

  console.log(file.name); // File name
  console.log(file.size); // Size in bytes
  console.log(file.type); // MIME type
  console.log(file.lastModified); // Timestamp
});

// Create File programmatically
const file = new File(['File content here'], 'example.txt', {
  type: 'text/plain',
  lastModified: Date.now(),
});

FileReader API

// Read file as text
function readAsText(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.onerror = () => reject(reader.error);
    reader.readAsText(file);
  });
}

// Read file as data URL (base64)
function readAsDataURL(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.onerror = () => reject(reader.error);
    reader.readAsDataURL(file);
  });
}

// Read file as ArrayBuffer
function readAsArrayBuffer(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.onerror = () => reject(reader.error);
    reader.readAsArrayBuffer(file);
  });
}

// Usage with progress
function readWithProgress(file, onProgress) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();

    reader.onprogress = (e) => {
      if (e.lengthComputable) {
        onProgress((e.loaded / e.total) * 100);
      }
    };

    reader.onload = () => resolve(reader.result);
    reader.onerror = () => reject(reader.error);
    reader.readAsArrayBuffer(file);
  });
}

Blob URLs

// Create a Blob URL
const blob = new Blob(['Hello, World!'], { type: 'text/plain' });
const blobUrl = URL.createObjectURL(blob);

// Use Blob URL
const link = document.createElement('a');
link.href = blobUrl;
link.download = 'hello.txt';
link.click();

// Always revoke when done
URL.revokeObjectURL(blobUrl);

// Image preview example
function previewImage(file) {
  const url = URL.createObjectURL(file);
  const img = document.createElement('img');
  img.src = url;
  img.onload = () => URL.revokeObjectURL(url);
  return img;
}

Data Conversion

Text Conversions

// Blob to Text
async function blobToText(blob) {
  return blob.text(); // Modern browsers
  // Or: return new Response(blob).text();
}

// Text to Blob
function textToBlob(text, type = 'text/plain') {
  return new Blob([text], { type });
}

// Blob to Base64
async function blobToBase64(blob) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.onerror = reject;
    reader.readAsDataURL(blob);
  });
}

// Base64 to Blob
function base64ToBlob(base64, type) {
  const byteCharacters = atob(base64.split(',')[1] || base64);
  const byteNumbers = new Uint8Array(byteCharacters.length);

  for (let i = 0; i < byteCharacters.length; i++) {
    byteNumbers[i] = byteCharacters.charCodeAt(i);
  }

  return new Blob([byteNumbers], { type });
}

ArrayBuffer Conversions

// Blob to ArrayBuffer
async function blobToArrayBuffer(blob) {
  return blob.arrayBuffer(); // Modern browsers
}

// ArrayBuffer to Blob
function arrayBufferToBlob(buffer, type) {
  return new Blob([buffer], { type });
}

// String to ArrayBuffer
function stringToArrayBuffer(str) {
  return new TextEncoder().encode(str).buffer;
}

// ArrayBuffer to String
function arrayBufferToString(buffer) {
  return new TextDecoder().decode(buffer);
}

Blob Slicing

// Slice a Blob
const largeBlob = new Blob(['0123456789'], { type: 'text/plain' });

const slice = largeBlob.slice(0, 5); // First 5 bytes
const middle = largeBlob.slice(3, 7); // Bytes 3-6
const end = largeBlob.slice(5); // From byte 5 to end

// Chunked reading
async function readInChunks(blob, chunkSize = 1024 * 1024) {
  const chunks = [];
  let offset = 0;

  while (offset < blob.size) {
    const chunk = blob.slice(offset, offset + chunkSize);
    const text = await chunk.text();
    chunks.push(text);
    offset += chunkSize;
  }

  return chunks;
}

File Downloads

// Download text file
function downloadText(content, filename) {
  const blob = new Blob([content], { type: 'text/plain' });
  const url = URL.createObjectURL(blob);

  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.click();

  URL.revokeObjectURL(url);
}

// Download JSON
function downloadJSON(data, filename) {
  const json = JSON.stringify(data, null, 2);
  const blob = new Blob([json], { type: 'application/json' });
  downloadBlob(blob, filename);
}

// Download Blob
function downloadBlob(blob, filename) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
}

// Download from fetch response
async function downloadFromUrl(url, filename) {
  const response = await fetch(url);
  const blob = await response.blob();
  downloadBlob(blob, filename);
}

File Uploads

// Upload with FormData
async function uploadFile(file, url) {
  const formData = new FormData();
  formData.append('file', file);
  formData.append('name', file.name);

  const response = await fetch(url, {
    method: 'POST',
    body: formData,
  });

  return response.json();
}

// Upload with progress
function uploadWithProgress(file, url, onProgress) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    const formData = new FormData();
    formData.append('file', file);

    xhr.upload.onprogress = (e) => {
      if (e.lengthComputable) {
        onProgress((e.loaded / e.total) * 100);
      }
    };

    xhr.onload = () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(JSON.parse(xhr.responseText));
      } else {
        reject(new Error(xhr.statusText));
      }
    };

    xhr.onerror = () => reject(new Error('Upload failed'));

    xhr.open('POST', url);
    xhr.send(formData);
  });
}

// Chunked upload
async function uploadInChunks(file, url, chunkSize = 1024 * 1024) {
  const totalChunks = Math.ceil(file.size / chunkSize);

  for (let i = 0; i < totalChunks; i++) {
    const start = i * chunkSize;
    const end = Math.min(start + chunkSize, file.size);
    const chunk = file.slice(start, end);

    const formData = new FormData();
    formData.append('chunk', chunk);
    formData.append('chunkIndex', i);
    formData.append('totalChunks', totalChunks);
    formData.append('fileName', file.name);

    await fetch(url, {
      method: 'POST',
      body: formData,
    });
  }
}

Image Processing

// Resize image
async function resizeImage(file, maxWidth, maxHeight) {
  return new Promise((resolve) => {
    const img = new Image();
    const url = URL.createObjectURL(file);

    img.onload = () => {
      URL.revokeObjectURL(url);

      let { width, height } = img;

      if (width > maxWidth) {
        height *= maxWidth / width;
        width = maxWidth;
      }

      if (height > maxHeight) {
        width *= maxHeight / height;
        height = maxHeight;
      }

      const canvas = document.createElement('canvas');
      canvas.width = width;
      canvas.height = height;

      const ctx = canvas.getContext('2d');
      ctx.drawImage(img, 0, 0, width, height);

      canvas.toBlob(resolve, file.type, 0.9);
    };

    img.src = url;
  });
}

// Compress image
async function compressImage(file, quality = 0.7) {
  return new Promise((resolve) => {
    const img = new Image();
    const url = URL.createObjectURL(file);

    img.onload = () => {
      URL.revokeObjectURL(url);

      const canvas = document.createElement('canvas');
      canvas.width = img.width;
      canvas.height = img.height;

      const ctx = canvas.getContext('2d');
      ctx.drawImage(img, 0, 0);

      canvas.toBlob(resolve, 'image/jpeg', quality);
    };

    img.src = url;
  });
}

File Type Detection

// Check by extension
function getFileType(filename) {
  const ext = filename.split('.').pop().toLowerCase();
  const types = {
    jpg: 'image/jpeg',
    jpeg: 'image/jpeg',
    png: 'image/png',
    gif: 'image/gif',
    pdf: 'application/pdf',
    json: 'application/json',
    txt: 'text/plain',
    html: 'text/html',
    css: 'text/css',
    js: 'application/javascript',
  };
  return types[ext] || 'application/octet-stream';
}

// Check by magic bytes
async function detectFileType(file) {
  const header = file.slice(0, 4);
  const buffer = await header.arrayBuffer();
  const bytes = new Uint8Array(buffer);

  // Check magic numbers
  if (bytes[0] === 0xff && bytes[1] === 0xd8) {
    return 'image/jpeg';
  }
  if (
    bytes[0] === 0x89 &&
    bytes[1] === 0x50 &&
    bytes[2] === 0x4e &&
    bytes[3] === 0x47
  ) {
    return 'image/png';
  }
  if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46) {
    return 'image/gif';
  }
  if (
    bytes[0] === 0x25 &&
    bytes[1] === 0x50 &&
    bytes[2] === 0x44 &&
    bytes[3] === 0x46
  ) {
    return 'application/pdf';
  }

  return file.type || 'application/octet-stream';
}

Best Practices

  1. Revoke Blob URLs - Prevent memory leaks
  2. Handle large files - Use chunked reading/uploading
  3. Validate file types - Check MIME types and magic bytes
  4. Show progress - Provide feedback for large operations
  5. Error handling - Handle read/upload failures gracefully

Summary

ConceptDescription
BlobRaw binary data with optional type
FileBlob with name and metadata
FileReaderRead file contents asynchronously
Blob URLTemporary URL for blob data
DataURLBase64-encoded data inline

Resources

.4 Blob File APIs - JavaScript Tutorial | DeepML