Dekodowanie Base64 w JavaScript — atob() i Buffer

·Front-end & Node.js Developer·Sprawdzono przezSophie Laurent·Opublikowano

Użyj darmowego Dekoder Base64 Online bezpośrednio w przeglądarce — bez instalacji.

Wypróbuj Dekoder Base64 Online online →

Gdy debuguję problem z uwierzytelnianiem w środowisku produkcyjnym, pierwszą rzeczą, po którą sięgam, jest Base64 Decoder — payloady JWT, podpisy webhooków i zakodowane wartości konfiguracyjne kryją się w ciągach Base64. JavaScript oferuje dwa podstawowe wbudowane podejścia do dekodowania base64: atob() (przeglądarka + Node.js 16+) oraz Buffer.from(encoded, 'base64').toString() (Node.js) — i zachowują się bardzo różnie, gdy oryginalne dane zawierały znaki Unicode. Do szybkiego jednorazowego dekodowania bez pisania kodu Base64 Decoder ToolDecka radzi sobie z tym natychmiast w przeglądarce. Ten przewodnik obejmuje oba środowiska — skupiając się na Node.js 16+ i nowoczesnych przeglądarkach (Chrome 80+, Firefox 75+, Safari 14+) — z gotowymi do produkcji przykładami: odzyskiwanie UTF-8, warianty URL-safe, dekodowanie JWT, pliki, odpowiedzi API, strumienie Node.js i cztery błędy, które konsekwentnie produkują nieczytelne wyjście w rzeczywistych kodebazach.

  • atob(encoded) jest natywna w przeglądarce i dostępna globalnie w Node.js 16+, ale zwraca ciąg binarny — użyj TextDecoder, aby odzyskać tekst UTF-8 z treści zawierającej znaki powyżej ASCII.
  • Buffer.from(encoded, "base64").toString("utf8") to idiomatyczne podejście w Node.js, które automatycznie obsługuje UTF-8 bez dodatkowych kroków.
  • URL-safe Base64 (używana w JWT) zastępuje + na -, / na _ i usuwa padding =. Przywróć je przed wywołaniem atob() lub użyj Buffer.from(encoded, "base64url").toString() w Node.js 18+.
  • Przed dekodowaniem usuń białe znaki i znaki nowej linii — GitHub Contents API i wiele enkoderów MIME zawija wyjście Base64 co 60–76 znaków na wiersz.
  • Uint8Array.prototype.fromBase64() (TC39 Stage 3) jest już dostępna w Node.js 22+ i Chrome 130+ i ostatecznie ujednolici oba środowiska.

Czym jest dekodowanie Base64?

Dekodowanie Base64 jest operacją odwrotną do kodowania — konwertuje 64-znakową reprezentację ASCII z powrotem na oryginalne dane binarne lub tekst. Każde 4 znaki Base64 mapują się z powrotem na dokładnie 3 bajty. Znaki dopełnienia = na końcu zakodowanego ciągu informują dekoder, ile dodatkowych bajtów zostało dołączonych, aby dopełnić ostatnią grupę 3-bajtową.

Base64 nie jest szyfrowaniem — operacja jest całkowicie odwracalna przez każdego dysponującego zakodowanym ciągiem. Jej celem jest bezpieczeństwo transportu: protokoły i formaty przechowywania zaprojektowane dla 7-bitowego tekstu ASCII nie mogą obsługiwać dowolnych bajtów binarnych, a Base64 wypełnia tę lukę. Typowe scenariusze dekodowania JavaScript obejmują sprawdzanie payloadów JWT, rozpakowywanie zakodowanych w Base64 konfiguracji JSON ze zmiennych środowiskowych, wyodrębnianie binarnej zawartości plików z REST API i dekodowanie URI danych w przeglądarce.

Before · text
After · text
ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=
deploy-bot:sk-prod-a7f2c91e4b3d8

atob() — Natywna funkcja dekodowania przeglądarki

atob() (ASCII-to-binary) jest dostępna w przeglądarkach od IE10 i stała się globalną funkcją w Node.js 16.0 jako część inicjatywy kompatybilności WinterCG. Działa również natywnie w Deno, Bun i Cloudflare Workers — bez konieczności importu.

Funkcja zwraca ciąg binarny: ciąg JavaScript, w którym każdy znak ma wartość punktu kodowego równą jednej wartości surowego bajtu (0–255). To ma znaczenie: jeśli oryginalne dane zawierały tekst UTF-8 ze znakami powyżej U+007F (litery z akcentem, cyrylica, CJK, emoji), zwrócony ciąg jest surową sekwencją bajtów, a nie czytelnym tekstem. Użyj TextDecoder, aby go odzyskać (omówione w następnej sekcji).

Minimalny działający przykład

JavaScript (browser / Node.js 16+)
// Decoding an HTTP Basic Auth credential pair received in a request header
// Authorization: Basic ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=

function parseBasicAuth(header: string): { serviceId: string; apiKey: string } {
  const base64Part = header.replace(/^Basics+/i, '')
  const decoded    = atob(base64Part)
  const [serviceId, apiKey] = decoded.split(':')
  return { serviceId, apiKey }
}

const auth = parseBasicAuth('Basic ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=')

console.log(auth.serviceId) // deploy-bot
console.log(auth.apiKey)    // sk-prod-a7f2c91e4b3d8

Weryfikacja round-trip

JavaScript
// Verify lossless recovery for ASCII-only content
const original = 'service:payments region:eu-west-1 env:production'

const encoded = btoa(original)
const decoded = atob(encoded)

console.log(encoded)
// c2VydmljZTpwYXltZW50cyByZWdpb246ZXUtd2VzdC0xIGVudjpwcm9kdWN0aW9u

console.log(decoded === original) // true
Uwaga:atob() i btoa() są częścią WinterCG Minimum Common API — tej samej specyfikacji, która reguluje Fetch, URL i crypto w środowiskach niebędących przeglądarkami. Zachowują się identycznie w Node.js 16+, Bun, Deno i Cloudflare Workers.

Odzyskiwanie tekstu UTF-8 po dekodowaniu

Najczęstszą pułapką z atob() jest niezrozumienie jej typu zwracanego. Gdy oryginalny tekst był zakodowany jako UTF-8 przed Base64, atob() zwraca binarny ciąg Latin-1, a nie czytelny tekst:

JavaScript
// 'Алексей Иванов' was UTF-8 encoded then Base64 encoded before transmission
const encoded = '0JDQu9C10LrRgdC10Lkg0JjQstCw0L3QvtCy'

// ❌ atob() returns the raw UTF-8 bytes as a Latin-1 string — garbled output
console.log(atob(encoded))
// "Алексей Р˜РІР°РЅРѕРІ"  ← byte values misread as Latin-1

Prawidłowe podejście używa TextDecoder do interpretacji surowych bajtów jako UTF-8:

Podejście z TextDecoder — bezpieczne dla dowolnego wyjścia Unicode

JavaScript (browser + Node.js 16+)
// Unicode-safe Base64 decode utilities
function fromBase64(encoded: string): string {
  const binary = atob(encoded)
  const bytes  = Uint8Array.from(binary, ch => ch.charCodeAt(0))
  return new TextDecoder().decode(bytes)
}

function toBase64(text: string): string {
  const bytes = new TextEncoder().encode(text)
  const chars = Array.from(bytes, byte => String.fromCharCode(byte))
  return btoa(chars.join(''))
}

// Works with any language or script
const orderNote = 'Confirmed: 田中太郎 — São Paulo warehouse, qty: 250'
const encoded   = toBase64(orderNote)
const decoded   = fromBase64(encoded)

console.log(decoded === orderNote) // true
console.log(decoded)
// Confirmed: 田中太郎 — São Paulo warehouse, qty: 250
Uwaga:W Node.js pomiń krok TextDecoder całkowicie — użyj Buffer.from(encoded, 'base64').toString('utf8'). Automatycznie interpretuje zdekodowane bajty jako UTF-8 i jest szybsza dla dużych danych wejściowych.

Buffer.from() w Node.js — Kompletny przewodnik po dekodowaniu

W Node.js Buffer jest idiomatycznym API do wszystkich operacji binarnych, w tym dekodowania Base64. Obsługuje UTF-8 natywnie, zwraca właściwy Buffer (bezpieczny binarnie), a od Node.js 18 obsługuje skrót kodowania 'base64url' dla wariantów URL-safe.

Dekodowanie zmiennej środowiskowej konfiguracji

Node.js
// Server config stored as Base64 in an env variable (avoids JSON escaping in shell)
// DB_CONFIG=eyJob3N0IjoiZGItcHJpbWFyeS5pbnRlcm5hbCIsInBvcnQiOjU0MzIsImRhdGFiYXNlIjoiYW5hbHl0aWNzX3Byb2QiLCJtYXhDb25uZWN0aW9ucyI6MTAwfQ==

const raw = Buffer.from(process.env.DB_CONFIG!, 'base64').toString('utf8')
const dbConfig = JSON.parse(raw)

console.log(dbConfig.host)           // db-primary.internal
console.log(dbConfig.port)           // 5432
console.log(dbConfig.maxConnections) // 100

Przywracanie pliku binarnego z pliku .b64

Node.js
import { readFileSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'

// Read the Base64-encoded certificate and restore the original binary
const encoded = readFileSync(join(process.cwd(), 'dist', 'cert.b64'), 'utf8').trim()
const certBuf  = Buffer.from(encoded, 'base64')

writeFileSync('./ssl/server.crt', certBuf)

console.log(`Restored ${certBuf.length} bytes`)
// Restored 2142 bytes

Asynchroniczne dekodowanie z obsługą błędów

Node.js
import { readFile, writeFile } from 'node:fs/promises'

async function decodeBase64File(
  encodedPath: string,
  outputPath:  string,
): Promise<number> {
  try {
    const encoded = await readFile(encodedPath, 'utf8')
    const binary  = Buffer.from(encoded.trim(), 'base64')
    await writeFile(outputPath, binary)
    return binary.length
  } catch (err) {
    const code = (err as NodeJS.ErrnoException).code
    if (code === 'ENOENT') throw new Error(`File not found: ${encodedPath}`)
    if (code === 'EACCES') throw new Error(`Permission denied: ${encodedPath}`)
    throw err
  }
}

// Restore a PDF stored as Base64
const bytes = await decodeBase64File('./uploads/invoice.b64', './out/invoice.pdf')
console.log(`Decoded ${bytes} bytes — PDF restored`)

Funkcje dekodowania Base64 — Dokumentacja parametrów

Skrócona dokumentacja parametrów dwóch podstawowych natywnych API dekodowania, sformatowana jako tabela referencyjna do użycia podczas pisania lub przeglądania kodu.

atob(encodedData)

ParametrTypWymaganyOpis
encodedDatastringTakStandardowy ciąg Base64 używający znaków +, /, =. Warianty URL-safe (-, _) rzucają InvalidCharacterError. Białe znaki są niedozwolone.
Zwraca: ciąg binarny — punkt kodowy każdego znaku jest równy jednej surowej wartości bajtu (0–255). Nie jest to ciąg Unicode; przekaż przez TextDecoder, aby odzyskać tekst UTF-8.

Buffer.from(input, inputEncoding) / .toString(outputEncoding)

ParametrTypDomyślnieOpis
inputstring | Buffer | TypedArray | ArrayBufferrequiredZakodowany w Base64 ciąg do zdekodowania lub bufor zawierający zakodowane bajty.
inputEncodingBufferEncoding"utf8"Ustaw na "base64" dla standardowego Base64 (RFC 4648 §4) lub "base64url" dla URL-safe Base64 (RFC 4648 §5, Node.js 18+).
outputEncodingstring"utf8"Kodowanie dla wyjścia .toString(). Użyj "utf8" dla czytelnego tekstu, "binary" dla binarnego ciągu Latin-1 zgodnego z wyjściem atob().
startinteger0Przesunięcie bajtowe w zdekodowanym Buforze, od którego zacząć czytać. Przekazywane do .toString() jako drugi argument.
endintegerbuf.lengthPrzesunięcie bajtowe, w którym zatrzymać czytanie (wyłączne). Przekazywane do .toString() jako trzeci argument.
Zwraca: Buffer z .from(). Zwraca string z .toString(). Zachowaj jako Buffer (nie wywołuj .toString()), gdy zdekodowana treść jest binarna — obrazy, PDF, audio.

URL-safe Base64 — Dekodowanie JWT i parametrów URL

JWT używają URL-safe Base64 (RFC 4648 §5) dla wszystkich trzech segmentów. URL-safe Base64 zastępuje + na - oraz / na _ i usuwa końcowy padding =. Bezpośrednie przekazanie tego do atob() bez przywrócenia znaków produkuje nieprawidłowe wyjście lub rzuca wyjątek.

Przeglądarka — przywrócenie znaków i dopełnienia przed dekodowaniem

JavaScript (browser)
function decodeBase64Url(input: string): string {
  const base64 = input.replace(/-/g, '+').replace(/_/g, '/')
  const padded = base64 + '==='.slice(0, (4 - base64.length % 4) % 4)
  const binary = atob(padded)
  const bytes  = Uint8Array.from(binary, ch => ch.charCodeAt(0))
  return new TextDecoder().decode(bytes)
}

// Inspect a JWT payload segment (the middle part between the two dots)
const jwtToken  = 'eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciIsIndvcmtzcGFjZUlkIjoid3NfM2E3ZjkxYzIiLCJleHAiOjE3MTcyMDM2MDB9'
const payload   = decodeBase64Url(jwtToken)
const claims    = JSON.parse(payload)

console.log(claims.userId)      // usr_9f2a1c3e8b4d
console.log(claims.role)        // editor
console.log(claims.workspaceId) // ws_3a7f91c2

Node.js 18+ — natywne kodowanie 'base64url'

Node.js 18+
// Node.js 18 added 'base64url' as a first-class Buffer encoding — no manual replace needed
function decodeJwtSegment(segment: string): Record<string, unknown> {
  const json = Buffer.from(segment, 'base64url').toString('utf8')
  return JSON.parse(json)
}

const token   = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciIsIndvcmtzcGFjZUlkIjoid3NfM2E3ZjkxYzIiLCJleHAiOjE3MTcyMDM2MDB9.SIGNATURE'
const [headerB64, payloadB64] = token.split('.')

const header  = decodeJwtSegment(headerB64)
const payload = decodeJwtSegment(payloadB64)

console.log(header.alg)          // HS256
console.log(payload.role)        // editor
console.log(payload.workspaceId) // ws_3a7f91c2

Dekodowanie Base64 z plików i odpowiedzi API

W kodzie produkcyjnym dekodowanie Base64 najczęściej zachodzi podczas konsumowania zewnętrznych API dostarczających treść w zakodowanej formie. Oba scenariusze mają ważne pułapki dotyczące białych znaków i wyjścia binarnego vs tekstowego. Jeśli potrzebujesz tylko sprawdzić zakodowaną odpowiedź podczas debugowania, wklej ją bezpośrednio do Base64 Decodera — natychmiast obsługuje tryby standardowy i URL-safe.

Dekodowanie treści z GitHub Contents API

JavaScript
// GitHub Contents API returns file content as Base64, wrapped at 60 chars per line
async function fetchDecodedFile(
  owner: string,
  repo:  string,
  path:  string,
  token: string,
): Promise<string> {
  const res = await fetch(
    `https://api.github.com/repos/${owner}/${repo}/contents/${path}`,
    { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github.v3+json' } }
  )
  if (!res.ok) throw new Error(`GitHub API ${res.status}: ${res.statusText}`)

  const data = await res.json() as { content: string; encoding: string }
  if (data.encoding !== 'base64') throw new Error(`Unexpected encoding: ${data.encoding}`)

  // ⚠️ GitHub wraps at 60 chars — strip newlines before decoding
  const clean = data.content.replace(/\n/g, '')
  return Buffer.from(clean, 'base64').toString('utf8')
}

const openApiSpec = await fetchDecodedFile('acme-corp', 'platform-api', 'openapi.json', process.env.GITHUB_TOKEN!)
const spec = JSON.parse(openApiSpec)
console.log(`API version: ${spec.info.version}`)

Dekodowanie binarnego pliku zakodowanego w Base64 z API (przeglądarka)

JavaScript (browser)
// Some APIs return binary content (images, PDFs) as Base64 JSON fields
async function downloadDecodedFile(endpoint: string, authToken: string): Promise<void> {
  const res = await fetch(endpoint, { headers: { Authorization: `Bearer ${authToken}` } })
  if (!res.ok) throw new Error(`Download failed: ${res.status}`)

  const { filename, content, mimeType } = await res.json() as {
    filename: string; content: string; mimeType: string
  }

  // Decode Base64 → binary bytes → Blob
  const binary = atob(content)
  const bytes  = Uint8Array.from(binary, ch => ch.charCodeAt(0))
  const blob   = new Blob([bytes], { type: mimeType })

  // Trigger browser download
  const url = URL.createObjectURL(blob)
  const a   = Object.assign(document.createElement('a'), { href: url, download: filename })
  a.click()
  URL.revokeObjectURL(url)
}

await downloadDecodedFile('/api/reports/latest', sessionStorage.getItem('auth_token')!)

Dekodowanie Base64 w wierszu poleceń w Node.js i shellu

Do skryptów CI/CD, sesji debugowania lub jednorazowych zadań dekodowania narzędzia shellowe i jednolinijkowce Node.js są szybsze niż pełny skrypt. Należy pamiętać, że nazwa flagi różni się między macOS a Linuxem.

bash
# ── macOS / Linux system base64 ───────────────────────────────────────
# Standard decoding (macOS uses -D, Linux uses -d)
echo "ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=" | base64 -d   # Linux
echo "ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=" | base64 -D   # macOS

# Decode a .b64 file to its original binary
base64 -d ./dist/cert.b64 > ./ssl/server.crt       # Linux
base64 -D -i ./dist/cert.b64 -o ./ssl/server.crt   # macOS

# URL-safe Base64 — restore + and / before decoding
echo "eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ" | tr '-_' '+/' | base64 -d

# ── Node.js one-liner — works on Windows too ───────────────────────────
node -e "process.stdout.write(Buffer.from(process.argv[1], 'base64').toString())" "ZGVwbG95LWJvdA=="
# deploy-bot

# URL-safe (Node.js 18+)
node -e "process.stdout.write(Buffer.from(process.argv[1], 'base64url').toString())" "eyJhbGciOiJIUzI1NiJ9"
# {"alg":"HS256"}
Uwaga:Na macOS polecenie base64 używa flagi -D do dekodowania (wielka litera D), podczas gdy Linux używa -d (mała litera). To po cichu niszczy skrypty CI — użyj jednolinijkowca Node.js, gdy nie ma gwarancji, że platforma docelowa będzie Linuxem.

Wydajna alternatywa: js-base64

Głównym powodem sięgnięcia po bibliotekę jest spójność między środowiskami. Jeśli dostarczasz pakiet działający zarówno w przeglądarce, jak i w Node.js bez konfiguracji bundlera, Buffer wymaga wykrywania środowiska, a atob() wymaga obejścia przez TextDecoder.js-base64 (100M+ cotygodniowych pobrań npm) obsługuje oba transparentnie.

bash
npm install js-base64
# or
pnpm add js-base64
JavaScript
import { fromBase64, fromBase64Url, isValid } from 'js-base64'

// Standard decoding — Unicode-safe, works in browser and Node.js
const raw   = fromBase64('eyJldmVudElkIjoiZXZ0XzdjM2E5ZjFiMmQiLCJ0eXBlIjoiY2hlY2tvdXRfY29tcGxldGVkIiwiY3VycmVuY3kiOiJFVVIiLCJhbW91bnQiOjE0OTAwfQ==')
const event = JSON.parse(raw)
console.log(event.type)     // checkout_completed
console.log(event.currency) // EUR

// URL-safe decoding — no manual character replacement needed
const jwtPayload = fromBase64Url('eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciJ9')
const claims     = JSON.parse(jwtPayload)
console.log(claims.role) // editor

// Validate before decoding untrusted input
const untrusted = 'not!valid@base64#'
if (!isValid(untrusted)) {
  console.error('Rejected: invalid Base64 input')
}

// Binary output — second argument true returns Uint8Array
const pngBytes = fromBase64('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', true)
console.log(pngBytes instanceof Uint8Array) // true

Wyjście terminala z podświetlaniem składni

Przy pisaniu narzędzi CLI do debugowania lub skryptów inspekcyjnych proste wyjście console.log jest trudne do odczytania dla dużych payloadów JSON. chalk (najczęściej pobierany pakiet npm do kolorowania terminala) w połączeniu z dekodowaniem Base64 produkuje czytelne, przejrzyste wyjście terminala — przydatne do inspekcji JWT, debugowania odpowiedzi API i audytu konfiguracji.

bash
npm install chalk
# chalk v5+ is ESM-only — use import, not require
Node.js
import chalk from 'chalk'

// Decode and display any Base64 value with smart type detection
function inspectBase64(encoded: string, label = 'Decoded value'): void {
  let decoded: string
  try {
    decoded = Buffer.from(encoded.trim(), 'base64').toString('utf8')
  } catch {
    console.error(chalk.red('✗ Invalid Base64 input'))
    return
  }

  console.log(chalk.bold.cyan(`\n── ${label} ──`))

  // Attempt JSON pretty-print
  try {
    const parsed = JSON.parse(decoded)
    console.log(chalk.green('Type:'), chalk.yellow('JSON'))
    for (const [key, value] of Object.entries(parsed)) {
      const display = typeof value === 'object' ? JSON.stringify(value) : String(value)
      console.log(chalk.green(`  ${key}:`), chalk.white(display))
    }
    return
  } catch { /* not JSON */ }

  // Plain text fallback
  console.log(chalk.green('Type:'), chalk.yellow('text'))
  console.log(chalk.white(decoded))
}

// Inspect a Base64-encoded JWT payload
const tokenParts = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciIsImV4cCI6MTcxNzIwMzYwMH0.SIGNATURE'.split('.')
inspectBase64(tokenParts[0], 'JWT Header')
inspectBase64(tokenParts[1], 'JWT Payload')
// ── JWT Header ──
// Type:   JSON
//   alg:  HS256
//   typ:  JWT
//
// ── JWT Payload ──
// Type:   JSON
//   userId: usr_9f2a1c3e8b4d
//   role:   editor
//   exp:    1717203600
Uwaga:Chalk używaj tylko do wyjścia terminala/CLI — nigdy do treści zapisywanych do plików, odpowiedzi API ani agregatorów logów. Kody escape ANSI psują odbiorniki nieterminalne: platformy logowania (Datadog, Splunk), parsery logów JSON i przeglądarki logów CI wszystkie wyświetlają je jako nieczytelne sekwencje znaków.

Dekodowanie dużych plików Base64 za pomocą strumieni Node.js

Gdy plik zakodowany w Base64 przekracza ~50 MB, ładowanie go całego do pamięci za pomocą readFileSync() staje się problemem. Strumienie Node.js pozwalają dekodować dane fragmentami — ale Base64 wymaga wielokrotności 4 znaków na fragment (każda 4-znakowa grupa dekoduje się dokładnie na 3 bajty), aby uniknąć błędów dopełnienia na granicach fragmentów.

Node.js
import { createReadStream, createWriteStream } from 'node:fs'
import { pipeline } from 'node:stream/promises'

async function streamDecodeBase64(inputPath: string, outputPath: string): Promise<void> {
  const readStream  = createReadStream(inputPath, { encoding: 'utf8', highWaterMark: 4 * 1024 * 192 })
  const writeStream = createWriteStream(outputPath)

  let buffer = ''

  await pipeline(
    readStream,
    async function* (source) {
      for await (const chunk of source) {
        buffer += (chunk as string).replace(/\s/g, '') // strip any whitespace/newlines

        // Decode only complete 4-char groups to avoid mid-stream padding issues
        const remainder = buffer.length % 4
        const safe      = buffer.slice(0, buffer.length - remainder)
        buffer          = buffer.slice(buffer.length - remainder)

        if (safe.length > 0) yield Buffer.from(safe, 'base64')
      }
      if (buffer.length > 0) yield Buffer.from(buffer, 'base64')
    },
    writeStream,
  )
}

// Decode a 200 MB video that was stored as Base64
await streamDecodeBase64('./uploads/product-demo.b64', './dist/product-demo.mp4')
console.log('Stream decode complete')
Uwaga:Rozmiar fragmentu musi być wielokrotnością 4 znaków podczas czytania tekstu Base64, aby każdy fragment zawierał tylko kompletne 4-znakowe grupy. Przykład używa 4 × 1024 × 192 = 786 432 znaków (768 KB). Dla plików poniżej 50 MB readFile() + Buffer.from(content.trim(), 'base64') jest prostsze i wystarczająco szybkie.

Częste błędy

Te cztery błędy w kodebazach JavaScript widzę wielokrotnie — mają tendencję do pozostawania ukrytymi, dopóki znak non-ASCII lub zawinięta wierszowo odpowiedź API nie dotrze do ścieżki dekodowania w środowisku produkcyjnym.

Błąd 1 — Użycie atob() bez TextDecoder dla treści UTF-8

Problem: atob() zwraca ciąg binarny, gdzie każdy znak to jedna surowa wartość bajtu. Wielobajtowe sekwencje UTF-8 (cyrylica, CJK, litery z akcentem) pojawiają się jako nieczytelne znaki Latin-1. Rozwiązanie: owiń wyjście w TextDecoder.

Before · JavaScript
After · JavaScript
// ❌ atob() returns the raw UTF-8 bytes as a Latin-1 string
const encoded = '0JDQu9C10LrRgdC10Lkg0JjQstCw0L3QvtCy'
const decoded  = atob(encoded)
console.log(decoded)
// "Алексей Р˜РІР°РЅРѕРІ"  ← wrong
// ✅ Use TextDecoder to correctly interpret the UTF-8 bytes
const encoded  = '0JDQu9C10LrRgdC10Lkg0JjQstCw0L3QvtCy'
const binary   = atob(encoded)
const bytes    = Uint8Array.from(binary, ch => ch.charCodeAt(0))
const decoded  = new TextDecoder().decode(bytes)
console.log(decoded) // Алексей Иванов ✓

Błąd 2 — Przekazanie URL-safe Base64 bezpośrednio do atob()

Problem: Segmenty JWT używają - i _ zamiast + i /, bez dopełnienia. atob() może zwrócić nieprawidłowe dane lub rzucić wyjątek. Rozwiązanie: najpierw przywróć standardowe znaki i dodaj dopełnienie.

Before · JavaScript
After · JavaScript
// ❌ URL-safe JWT segment passed directly — unreliable
const jwtPayload = 'eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ'
const decoded    = atob(jwtPayload) // May produce wrong result or throw
// ✅ Restore standard Base64 chars and padding first
function decodeBase64Url(input: string): string {
  const b64  = input.replace(/-/g, '+').replace(/_/g, '/')
  const pad  = b64 + '==='.slice(0, (4 - b64.length % 4) % 4)
  const bin  = atob(pad)
  const bytes = Uint8Array.from(bin, ch => ch.charCodeAt(0))
  return new TextDecoder().decode(bytes)
}
const decoded = decodeBase64Url('eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ')
// {"userId":"usr_9f2a1c3e"} ✓

Błąd 3 — Nieusunięcie znaków nowej linii z zawinięnego wierszowo Base64

Problem: GitHub Contents API i enkodery MIME zawijają wyjście Base64 co 60–76 znaków na wiersz. atob() rzuca InvalidCharacterError dla znaków \n. Rozwiązanie: przed dekodowaniem usuń wszystkie białe znaki.

Before · JavaScript
After · JavaScript
// ❌ GitHub API content field contains embedded newlines
const data    = await res.json()
const decoded = atob(data.content) // ❌ throws InvalidCharacterError
// ✅ Strip newlines (and any other whitespace) before decoding
const data    = await res.json()
const clean   = data.content.replace(/\s/g, '')
const decoded = atob(clean) // ✓

Błąd 4 — Wywołanie .toString() na zdekodowanej treści binarnej

Problem: Gdy oryginalne dane są binarne (obrazy, PDF, audio), wywołanie .toString('utf8') zastępuje nierozpoznane sekwencje bajtów znakiem U+FFFD, po cichu uszkadzając wyjście. Rozwiązanie: zachowaj wynik jako Buffer — nie konwertuj go na ciąg.

Before · JavaScript
After · JavaScript
// ❌ .toString('utf8') corrupts binary content
import { readFileSync, writeFileSync } from 'node:fs'
const encoded   = readFileSync('./uploads/invoice.b64', 'utf8').trim()
const corrupted = Buffer.from(encoded, 'base64').toString('utf8') // ❌
writeFileSync('./out/invoice.pdf', corrupted) // ❌ unreadable PDF
// ✅ Keep the Buffer as binary — do not convert to a string
import { readFileSync, writeFileSync } from 'node:fs'
const encoded = readFileSync('./uploads/invoice.b64', 'utf8').trim()
const binary  = Buffer.from(encoded, 'base64') // ✓ raw bytes preserved
writeFileSync('./out/invoice.pdf', binary)      // ✓ valid PDF

Metody dekodowania Base64 w JavaScript — szybkie porównanie

MetodaWyjście UTF-8Wyjście binarneURL-safeŚrodowiskaWymaga instalacji
atob()❌ wymaga TextDecoder✅ ciąg binarny❌ ręczne przywróceniePrzeglądarka, Node 16+, Bun, DenoNie
TextDecoder + atob()✅ UTF-8✅ przez Uint8Array❌ ręczne przywróceniePrzeglądarka, Node 16+, DenoNie
Buffer.from().toString()✅ utf8✅ zachowaj jako Buffer✅ base64url (Node 18+)Node.js, BunNie
Uint8Array.fromBase64() (TC39)✅ przez TextDecoder✅ natywne✅ opcja alphabetChrome 130+, Node 22+Nie
js-base64✅ zawsze✅ Uint8Array✅ wbudowaneUniwersalnenpm install

Wybierz atob() tylko wtedy, gdy zdekodowana treść jest gwarantowanie tekstem ASCII. Dla dowolnego tekstu podanego przez użytkownika lub wielojęzycznego tekstu w przeglądarce użyj TextDecoder + atob(). Dla kodu po stronie serwera w Node.js Buffer jest właściwym domyślnym wyborem — obsługuje UTF-8 automatycznie i zachowuje dane binarne. Dla bibliotek działających w różnych środowiskach js-base64 usuwa wszystkie przypadki brzegowe.

Często zadawane pytania

Dlaczego atob() zwraca nieczytelne znaki zamiast czytelnego tekstu?
atob() zwraca ciąg binarny, gdzie każdy znak reprezentuje jeden surowy bajt (0–255), a nie punkt kodowy Unicode. Jeśli oryginalny tekst był zakodowany jako UTF-8, każdy znak powyżej U+007F — cyrylica, arabski, ideografy CJK, litery z akcentem — pojawi się jako dwa lub więcej nieczytelnych znaków Latin-1. Rozwiązanie: przekaż wyjście przez TextDecoder: const bytes = Uint8Array.from(atob(encoded), ch => ch.charCodeAt(0)); const text = new TextDecoder().decode(bytes). W Node.js użyj Buffer.from(encoded, 'base64').toString('utf8'), które obsługuje to automatycznie.
Jak zdekodować payload tokenu JWT w JavaScript?
JWT ma trzy URL-safe Base64 segmenty oddzielone kropkami: header.payload.signature. Aby zdekodować payload: const [, payloadB64] = token.split('.'). W przeglądarce: przywróć standardowe znaki, dodaj dopełnienie, zdekoduj za pomocą atob() i TextDecoder. W Node.js 18+: Buffer.from(payloadB64, 'base64url').toString('utf8'). Ważne: dekodowanie tylko ujawnia claims — NIE weryfikuje podpisu. Do weryfikowanego dekodowania w środowisku produkcyjnym użyj właściwej biblioteki JWT (jsonwebtoken, jose).
Jaka jest różnica między atob() a Buffer.from() do dekodowania?
atob() jest dostępna we wszystkich środowiskach JavaScript (przeglądarka, Node.js 16+, Bun, Deno) bez importów, ale zwraca ciąg binarny — potrzebujesz TextDecoder, aby przekonwertować treść UTF-8 na czytelny tekst. Buffer.from(encoded, 'base64') jest tylko dla Node.js / Bun, zwraca właściwy Buffer (bezpieczny binarnie), natywnie obsługuje UTF-8 i obsługuje 'base64url' w Node.js 18+. Dla kodu po stronie serwera Buffer jest prostszy. Dla kodu przeglądarki standardem jest atob() + TextDecoder. Dla bibliotek działających w różnych środowiskach js-base64 abstrahuje różnicę.
Jak zdekodować URL-safe Base64 w przeglądarce?
URL-safe Base64 zastępuje + na -, / na _ i usuwa dopełnienie =. Przywróć je przed wywołaniem atob(): const b64 = input.replace(/-/g, '+').replace(/_/g, '/'); const padded = b64 + '==='.slice(0, (4 - b64.length % 4) % 4); const text = new TextDecoder().decode(Uint8Array.from(atob(padded), c => c.charCodeAt(0))). W Node.js 18+: Buffer.from(input, 'base64url').toString('utf8') obsługuje to w jednym wywołaniu.
Jak zdekodować treść Base64 z GitHub API w JavaScript?
GitHub Contents API zwraca zawartość pliku jako standardowy Base64 ze znakami nowej linii co 60 znaków. Usuń je przed dekodowaniem: const clean = data.content.replace(/\n/g, ''). W przeglądarce: new TextDecoder().decode(Uint8Array.from(atob(clean), c => c.charCodeAt(0))). W Node.js: Buffer.from(clean, 'base64').toString('utf8'). Dla plików binarnych (obrazy, PDF) zachowaj Buffer bez wywoływania .toString() — przekaż go bezpośrednio do writeFile lub strumienia odpowiedzi.
Czy mogę zdekodować obraz zakodowany w Base64 w JavaScript bez biblioteki?
Tak. W przeglądarce: const binary = atob(encoded); const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0)); const blob = new Blob([bytes], { type: 'image/png' }); const url = URL.createObjectURL(blob). Dla src obrazu zbuduj zamiast tego URI danych: const src = 'data:image/png;base64,' + encoded — to całkowicie pomija krok dekodowania. W Node.js: Buffer.from(encoded, 'base64') a następnie writeFileSync('./out.png', buffer). Kluczowa zasada: nigdy nie wywoływać .toString() na zdekodowanym Buforze, gdy treść jest binarna.

Powiązane narzędzia

Do dekodowania jednym kliknięciem bez pisania kodu wklej ciąg Base64 bezpośrednio do Base64 Decodera — natychmiast obsługuje tryby standardowy i URL-safe z natychmiastowym wyjściem w przeglądarce.

Dostępne również w:PythonGoJavaC#
AC
Alex ChenFront-end & Node.js Developer

Alex is a front-end and Node.js developer with extensive experience building web applications and developer tooling. He is passionate about web standards, browser APIs, and the JavaScript ecosystem. In his spare time he contributes to open-source projects and writes about modern JavaScript patterns, performance optimisation, and everything related to the web platform.

SL
Sophie LaurentRecenzent techniczny

Sophie is a full-stack developer focused on TypeScript across the entire stack — from React frontends to Express and Fastify backends. She has a particular interest in type-safe API design, runtime validation, and the patterns that make large JavaScript codebases stay manageable. She writes about TypeScript idioms, Node.js internals, and the ever-evolving JavaScript module ecosystem.