logo

Blob & File - working with binary data in JavaScript

Introduction

The Blob (Binary Large Object) and File APIs are fundamental for working with binary data in web applications. Whether you're handling file uploads, generating downloads, or working with media, understanding these APIs is essential for modern web development.

This guide covers:

  • URL construction and parsing with URLSearchParams
  • Creating and using Blob objects
  • Working with File objects from user uploads
  • Converting files to object URLs and base64
  • Real-world use cases and practical examples

URL basics

Working with URLSearchParams

The URL interface provides powerful tools for parsing, constructing, and manipulating URLs. The searchParams property gives access to query parameters:

const url = new URL('https://www.google.com:443/index.html?a=1&b=2#hash')

/**
 *  URL {
 *    href: "https://www.google.com:443/index.html?a=1&b=2#hash",
 *    protocol: "https:",
 *    username: "",
 *    password: "",
 *    hash: "#hash",
 *    origin: "https://www.google.com:443", // with port, with protocol
 *    host: "www.google.com:443",           // with port, without protocol
 *    hostname: "www.google.com",           // without port, without protocol
 *    port: "443",
 *    pathname: "/index.html",
 *    search: "?a=1&b=2",
 *    searchParams: URLSearchParams {}
 *  }
 */

// Read query parameters
url.searchParams.get('a') // '1'
url.searchParams.has('b') // true
url.searchParams.getAll('a') // ['1']

// Modify query parameters
url.searchParams.set('c', '3')
url.searchParams.delete('a')
url.searchParams.append('d', '4')

console.log(url.href) // Updated URL with new parameters

Use cases:

  • Reading query parameters from current page URL
  • Building URLs dynamically for API requests
  • Handling pagination, filters, and search parameters

Blob API

What is a Blob?

A Blob object represents immutable, raw binary data. Think of it as a container for file-like data that can be:

  • Read as text or binary
  • Converted to streams for processing
  • Used anywhere File objects are accepted

Creating Blobs

// From JSON data
const obj = { hello: 'world' }
const jsonBlob = new Blob([JSON.stringify(obj, null, 2)], { type: 'application/json' })

/**
 *  Blob {
 *    size: 22, // in bytes
 *    type: 'application/json'
 *  }
 */

// From text
const textBlob = new Blob(['Hello, world!'], { type: 'text/plain' })
// From array buffer (binary data)
const buffer = new ArrayBuffer(8)
const binaryBlob = new Blob([buffer], { type: 'application/octet-stream' })

How to read Blobs

FileReader API

You can read Blob data using the FileReader API:

const reader = new FileReader()
reader.onload = (e) => {
  console.log('Blob data as text:', e.target.result)
}
reader.readAsText(jsonBlob) // Read as text

Creating object URLs from Blobs

URL.createObjectURL() generates a temporary URL that references the Blob data:

const blob = new Blob([JSON.stringify({ hello: 'world' })], {
  type: 'application/json',
})

// Create a URL: "blob:https://example.com/ff415f41-2b1b-4e47-bc1c-37112e0ff4b2"
const blobUrl = URL.createObjectURL(blob)

// Use the URL (e.g., for downloads)
const link = document.createElement('a')
link.href = blobUrl
link.download = 'data.json'
link.click()

// Clean up - important for memory management!
URL.revokeObjectURL(blobUrl)

Important: Always revoke object URLs when done to prevent memory leaks. The URL remains valid until the document is unloaded or you explicitly revoke it.

File api

Understanding File objects

A File object is a specialized Blob with additional metadata like filename and modification date. Files typically come from:

  • <input type="file"> elements
  • Drag-and-drop operations
  • Programmatic creation for testing or data generation

Creating Files programmatically

const file = new File(
  ['hello', 'world'], // Array of content parts
  'test.txt', // Filename
  {
    type: 'text/plain',
    lastModified: Date.now(),
  }
)

/**
 * File {
 *   name: "test.txt",
 *   lastModified: 1594093337229,
 *   lastModifiedDate: Tue Jul 07 2020 11:42:17 GMT+0800 (China Standard Time),
 *   size: 10,
 *   type: "text/plain"
 * }
 */

FileReader API

FileReader reads file contents asynchronously and converts them to different formats:

const reader = new FileReader()

// Read as Data URL (base64)
reader.readAsDataURL(file)

// Read as text
reader.readAsText(file)

// Read as array buffer
reader.readAsArrayBuffer(file)

// Read as binary string (deprecated, use ArrayBuffer)
reader.readAsBinaryString(file)

Preview uploaded images with Object URLs

The most performant way to preview images - creates a temporary URL directly to the file:

import { useState } from 'react'

export default function App() {
  const [preview, setPreview] = useState<string>('')

  function handleFileChange(e) {
    const file = e.target.files[0]

    if (file && file.type.startsWith('image/')) {
      // Create object URL for preview
      const url = URL.createObjectURL(file)
      console.log('url', url)
      setPreview(url)
      // Clean up previous URL if exists
      return () => URL.revokeObjectURL(url)
    }
  }

  return (
    <div className="space-y-4">
      <label className="inline-block cursor-pointer rounded-lg border-2 border-blue-500 bg-blue-500 px-6 py-3 font-semibold text-white transition-all hover:border-blue-600 hover:bg-blue-600">
        <span>Choose Image</span>
        <input type="file" accept="image/*" onChange={handleFileChange} className="hidden" />
      </label>
      {preview && (
        <div>
          <h3 className="mb-2 text-lg font-semibold">Preview:</h3>
          <img
            src={preview}
            alt="Preview"
            className="max-w-md rounded-lg border border-gray-200 shadow-md dark:border-gray-700"
          />
        </div>
      )}
    </div>
  )
}

Advantages of Object URLs:

  • Instant preview (no encoding needed)
  • Memory efficient
  • No base64 bloat
  • Perfect for large files

Preview uploaded images with base64

When you need the actual data (e.g., for upload, storage, or manipulation):

import { useState } from 'react'

export default function App() {
  const [preview, setPreview] = useState<string>('')

  function handleFileChange(e) {
    const file = e.target.files[0]

    if (file && file.type.startsWith('image/')) {
      const reader = new FileReader()

      reader.onload = (e) => {
        console.log('result', e.target?.result)
        setPreview(e.target?.result as string)
      }

      reader.onerror = () => {
        console.error('Error reading file')
      }

      reader.readAsDataURL(file)
    }
  }

  return (
    <div className="space-y-4">
      <label className="inline-block cursor-pointer rounded-lg border-2 border-blue-500 bg-blue-500 px-6 py-3 font-semibold text-white transition-all hover:border-blue-600 hover:bg-blue-600">
        <span>Choose Image</span>
        <input type="file" accept="image/*" onChange={handleFileChange} className="hidden" />
      </label>
      {preview && (
        <div>
          <h3 className="mb-2 text-lg font-semibold">Preview:</h3>
          <img
            src={preview}
            alt="Preview"
            className="max-w-md rounded-lg border border-gray-200 shadow-md dark:border-gray-700"
          />
        </div>
      )}
    </div>
  )
}

When to use readAsDataURL:

  • Need base64 for APIs that require it
  • Storing images in localStorage/IndexedDB
  • Sending images in JSON payloads
  • Image manipulation before upload

Downsides:

  • Slower than object URLs
  • Large base64 strings (33% larger than binary)
  • Higher memory usage

Real world use case: pdf.js worker setup

When using libraries like pdf.js, you might encounter issues with worker file paths after bundling. The traditional approach of pointing to a static file path breaks when using webpack or other bundlers:

The problem

import pdfjsLib from 'pdfjs-dist'

// ❌ This breaks after bundling - path becomes incorrect
pdfjsLib.GlobalWorkerOptions.workerSrc = './node_modules/pdfjs-dist/build/pdf.worker.js'

Issues:

  • Relative paths don't work in production bundles
  • Worker file might not be copied to dist folder
  • CDN hosting complicates paths further

The solution: Blob URLs

Convert the worker code to a Blob and create an object URL:

import pdfjsLib from 'pdfjs-dist'
// Use raw-loader to import worker code as a string
import pdfjsWorker from 'raw-loader!pdfjs-dist/build/pdf.worker.min.js'

// Create Blob from worker code
const pdfjsWorkerBlob = new Blob([pdfjsWorker], {
  type: 'application/javascript',
})

// Generate object URL
const pdfjsWorkerBlobURL = URL.createObjectURL(pdfjsWorkerBlob)

// Set worker source
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorkerBlobURL

Best practices

1. Always clean up Object URLs

// ❌ Memory leak
const url = URL.createObjectURL(blob)
img.src = url

// ✅ Proper cleanup
const url = URL.createObjectURL(blob)
img.src = url
img.onload = () => URL.revokeObjectURL(url)

2. Validate file types

function handleFile(file) {
  const allowedTypes = ['image/png', 'image/jpeg', 'image/gif']

  if (!allowedTypes.includes(file.type)) {
    alert('Please upload PNG, JPEG, or GIF images only')
    return
  }

  // Process file...
}

3. Check file size

function handleFile(file) {
  const maxSize = 5 * 1024 * 1024 // 5MB

  if (file.size > maxSize) {
    alert('File size must be less than 5MB')
    return
  }

  // Process file...
}

4. Handle errors gracefully

const reader = new FileReader()

reader.onload = (e) => {
  // Success handler
}

reader.onerror = () => {
  console.error('Failed to read file:', reader.error)
  alert('Error reading file. Please try again.')
}

reader.onabort = () => {
  console.log('File reading aborted')
}

reader.readAsDataURL(file)

5. Use appropriate method for your use case

// Quick preview - use Object URL
function previewImage(file) {
  const url = URL.createObjectURL(file)
  img.src = url
  img.onload = () => URL.revokeObjectURL(url)
}

// Upload to API - use FormData (keeps as binary)
function uploadImage(file) {
  const formData = new FormData()
  formData.append('image', file)
  fetch('/upload', { method: 'POST', body: formData })
}

// Store in database - use base64
function storeImage(file) {
  const reader = new FileReader()
  reader.onload = (e) => {
    // Store e.target.result in database
  }
  reader.readAsDataURL(file)
}

Common use cases summary

Image upload preview: Use URL.createObjectURL() for instant, efficient preview File downloads: Create Blob with data, generate object URL, trigger download PDF worker setup: Bundle worker code as Blob to avoid path issues API uploads: Use FormData with File objects (no conversion needed) Storing in database: Use FileReader with base64 when binary storage isn't available

Conclusion

Understanding Blob and File APIs is crucial for modern web development. Key takeaways:

  • Object URLs are fastest for previews but need manual cleanup
  • FileReader provides flexibility for different data formats
  • Blobs can solve bundling issues with workers and assets
  • Always validate file types and sizes
  • Clean up resources to prevent memory leaks

These APIs form the foundation for file handling in web applications, from simple image uploads to complex document processing systems.