Giải Mã Base64 trong JavaScript: atob() & Buffer

·Front-end & Node.js Developer·Đánh giá bởiSophie Laurent·Đã xuất bản

Sử dụng Giải mã Base64 Trực tuyến miễn phí trực tiếp trên trình duyệt — không cần cài đặt.

Dùng thử Giải mã Base64 Trực tuyến trực tuyến →

Khi tôi debug một vấn đề xác thực trên môi trường production, thứ đầu tiên tôi dùng là bộ giải mã Base64 — JWT payload, webhook signature, và các giá trị config được mã hóa đều ẩn bên trong chuỗi Base64. JavaScript cung cấp hai cách tích hợp sẵn để base64 decode: atob() (browser + Node.js 16+) và Buffer.from(encoded, 'base64').toString() (Node.js) — và chúng hoạt động rất khác nhau khi dữ liệu gốc chứa ký tự Unicode. Để giải mã nhanh mà không cần viết code, Bộ giải mã Base64 của ToolDeck xử lý ngay lập tức trong trình duyệt của bạn. Hướng dẫn này bao gồm cả hai môi trường — nhắm đến Node.js 16+ và các trình duyệt hiện đại (Chrome 80+, Firefox 75+, Safari 14+) — với các ví dụ sẵn sàng cho production: khôi phục UTF-8, các biến thể URL-safe, giải mã JWT, file, phản hồi API, Node.js streams, và bốn lỗi thường xuyên tạo ra kết quả sai trong các codebase thực tế.

  • atob(encoded) có sẵn natively trong trình duyệt và trong Node.js 16+ toàn cục, nhưng trả về một binary string — dùng TextDecoder để khôi phục văn bản UTF-8 từ bất kỳ nội dung nào trên ASCII.
  • Buffer.from(encoded, "base64").toString("utf8") là cách tiếp cận chuẩn của Node.js và xử lý UTF-8 tự động mà không cần thêm bước nào.
  • URL-safe Base64 (dùng trong JWT) thay + bằng -, / bằng _, và bỏ padding =. Hãy khôi phục các ký tự này trước khi gọi atob(), hoặc dùng Buffer.from(encoded, "base64url").toString() trong Node.js 18+.
  • Loại bỏ khoảng trắng và ký tự xuống dòng trước khi giải mã — GitHub Contents API và nhiều MIME encoder ngắt dòng Base64 output tại 60–76 ký tự mỗi dòng.
  • Uint8Array.prototype.fromBase64() (TC39 Stage 3) đã có sẵn trong Node.js 22+ và Chrome 130+ và cuối cùng sẽ thống nhất cả hai môi trường.

Base64 Decoding là gì?

Base64 decoding là phép nghịch đảo của encoding — nó chuyển đổi biểu diễn ASCII 64 ký tự trở về dữ liệu nhị phân hoặc văn bản gốc. Mỗi 4 ký tự Base64 ánh xạ ngược lại chính xác 3 byte. Các ký tự padding = ở cuối chuỗi được mã hóa cho decoder biết bao nhiêu byte thêm đã được chèn vào để hoàn thành nhóm 3 byte cuối cùng.

Base64 không phải là mã hóa — thao tác này hoàn toàn có thể đảo ngược bởi bất kỳ ai có chuỗi đã mã hóa. Mục đích của nó là an toàn truyền tải: các giao thức và định dạng lưu trữ được thiết kế cho văn bản ASCII 7 bit không thể xử lý các byte nhị phân tùy ý, và Base64 lấp đầy khoảng trống đó. Các tình huống giải mã JavaScript phổ biến bao gồm kiểm tra JWT payload, giải nén cấu hình JSON được mã hóa Base64 từ biến môi trường, trích xuất nội dung file nhị phân từ REST API, và giải mã data URI trong trình duyệt.

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

atob() — Hàm Giải Mã Native của Trình Duyệt

atob() (ASCII-to-binary) đã có trong trình duyệt từ IE10 và trở thành biến toàn cục trong Node.js 16.0 như một phần của sáng kiến tương thích WinterCG. Nó cũng hoạt động natively trong Deno, Bun, và Cloudflare Workers — không cần import.

Hàm trả về một binary string: một chuỗi JavaScript trong đó mỗi ký tự có code point bằng một giá trị byte thô (0–255). Điều này quan trọng: nếu dữ liệu gốc là văn bản UTF-8 chứa các ký tự trên U+007F (chữ có dấu, Cyrillic, CJK, emoji), chuỗi được trả về là chuỗi byte thô, không phải văn bản có thể đọc được. Dùng TextDecoder để khôi phục nó (được đề cập ở phần tiếp theo).

Ví dụ tối thiểu hoạt động được

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

Xác minh 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
Lưu ý:atob()btoa() là một phần của WinterCG Minimum Common API — cùng một spec điều chỉnh Fetch, URL, và crypto trong các runtime không phải trình duyệt. Chúng hoạt động giống hệt nhau trong Node.js 16+, Bun, Deno, và Cloudflare Workers.

Khôi Phục Văn Bản UTF-8 Sau Khi Giải Mã

Lỗi phổ biến nhất với atob() là hiểu nhầm kiểu trả về của nó. Khi văn bản gốc được mã hóa UTF-8 trước Base64, atob() trả về binary string Latin-1, không phải văn bản có thể đọc:

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

Cách tiếp cận đúng là dùng TextDecoder để diễn giải các byte thô đó dưới dạng UTF-8:

Phương pháp TextDecoder — an toàn với mọi output 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
Lưu ý:Trong Node.js, bỏ qua hoàn toàn bước TextDecoder — dùng Buffer.from(encoded, 'base64').toString('utf8'). Hàm này tự động diễn giải các byte đã giải mã dưới dạng UTF-8 và nhanh hơn với input lớn.

Buffer.from() trong Node.js — Hướng Dẫn Giải Mã Toàn Diện

Trong Node.js, Buffer là API chuẩn cho mọi thao tác nhị phân bao gồm giải mã Base64. Nó xử lý UTF-8 natively, trả về một Buffer thực sự (an toàn nhị phân), và từ Node.js 18 hỗ trợ phím tắt encoding 'base64url' cho các biến thể URL-safe.

Giải mã config từ biến môi trường

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

Khôi phục file nhị phân từ file .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

Giải mã bất đồng bộ với xử lý lỗi

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`)

Hàm Giải Mã Base64 — Tham Chiếu Tham Số

Tham chiếu nhanh cho các tham số của hai API giải mã native chính, được định dạng để dùng tra cứu khi viết hoặc review code.

atob(encodedData)

Tham sốKiểuBắt buộcMô tả
encodedDatastringChuỗi Base64 chuẩn sử dụng các ký tự +, /, =. Các biến thể URL-safe (-, _) ném InvalidCharacterError. Khoảng trắng không được phép.
Trả về: binary string — code point của mỗi ký tự bằng một giá trị byte thô (0–255). Không phải chuỗi Unicode; truyền qua TextDecoder để khôi phục văn bản UTF-8.

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

Tham sốKiểuMặc địnhMô tả
inputstring | Buffer | TypedArray | ArrayBufferbắt buộcChuỗi được mã hóa Base64 để giải mã, hoặc buffer chứa các byte được mã hóa.
inputEncodingBufferEncoding"utf8"Đặt thành "base64" cho Base64 chuẩn (RFC 4648 §4), hoặc "base64url" cho URL-safe Base64 (RFC 4648 §5, Node.js 18+).
outputEncodingstring"utf8"Encoding cho output .toString(). Dùng "utf8" cho văn bản có thể đọc, "binary" cho binary string Latin-1 tương thích với output atob().
startinteger0Byte offset trong Buffer đã giải mã để bắt đầu đọc. Truyền vào .toString() làm tham số thứ hai.
endintegerbuf.lengthByte offset để dừng đọc (exclusive). Truyền vào .toString() làm tham số thứ ba.
Trả về: Buffer từ .from(). Trả về string từ .toString(). Giữ nguyên Buffer (đừng gọi .toString()) khi nội dung đã giải mã là nhị phân — ảnh, PDF, audio.

URL-safe Base64 — Giải Mã JWT và Tham Số URL

JWT sử dụng URL-safe Base64 (RFC 4648 §5) cho cả ba phân đoạn. URL-safe Base64 thay + bằng - / bằng _, và bỏ padding = ở cuối. Truyền trực tiếp vào atob() mà không khôi phục sẽ tạo ra kết quả sai hoặc ném lỗi.

Browser — khôi phục ký tự và padding trước khi giải mã

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+ — encoding 'base64url' native

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

Giải Mã Base64 từ File và Phản Hồi API

Trong code production, giải mã Base64 thường xảy ra nhất khi sử dụng các API bên ngoài trả về nội dung dưới dạng mã hóa. Cả hai tình huống đều có những gotcha quan trọng liên quan đến khoảng trắng và output nhị phân so với văn bản. Nếu bạn chỉ cần kiểm tra phản hồi được mã hóa trong khi debug, hãy dán trực tiếp vào Bộ giải mã Base64 — xử lý ngay lập tức cả chế độ chuẩn và URL-safe.

Giải mã nội dung từ 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}`)

Giải mã dữ liệu nhị phân Base64 từ API (browser)

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')!)

Giải Mã Base64 qua Command-Line Node.js và Shell

Đối với các script CI/CD, phiên debug, hoặc tác vụ giải mã một lần, công cụ shell và one-liner Node.js nhanh hơn một script đầy đủ. Lưu ý rằng tên flag khác nhau giữa macOS và Linux.

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"}
Lưu ý:Trên macOS, base64 dùng -D để giải mã (D hoa), trong khi Linux dùng -d (chữ thường). Điều này phá vỡ script CI một cách im lặng — hãy dùng one-liner Node.js khi không đảm bảo nền tảng đích là Linux.

Thư Viện Hiệu Năng Cao: js-base64

Lý do chính để dùng thư viện là tính nhất quán đa môi trường. Nếu bạn phát hành một package chạy trong cả browser và Node.js mà không cần cấu hình bundler, Buffer cần phát hiện môi trường và atob() cần workaround TextDecoder. js-base64 (100M+ lượt tải npm hàng tuần) xử lý cả hai một cách trong suốt.

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

Kết Quả Terminal với Tô Màu Cú Pháp

Khi viết công cụ debug CLI hoặc script kiểm tra, output console.log thông thường khó đọc với JSON payload lớn. chalk (package npm được tải nhiều nhất để tô màu terminal) kết hợp với giải mã Base64 tạo ra output terminal dễ đọc, dễ quét — hữu ích cho kiểm tra JWT, debug phản hồi API, và kiểm toán cấu hình.

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
Lưu ý:Chỉ dùng chalk cho output terminal/CLI — không bao giờ dùng cho nội dung ghi vào file, phản hồi API, hoặc log aggregator. Mã escape ANSI làm hỏng các consumer không phải terminal: nền tảng log (Datadog, Splunk), JSON log parser, và CI log viewer đều hiển thị chúng dưới dạng chuỗi ký tự không đọc được.

Giải Mã File Base64 Lớn với Node.js Streams

Khi file được mã hóa Base64 vượt quá ~50 MB, việc tải toàn bộ vào bộ nhớ bằng readFileSync() trở thành vấn đề. Node.js streams cho phép bạn giải mã dữ liệu theo từng phần — nhưng Base64 yêu cầu bội số của 4 ký tự mỗi phần (mỗi nhóm 4 ký tự giải mã đúng 3 byte) để tránh lỗi padding ở ranh giới chunk.

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')
Lưu ý:Kích thước chunk phải là bội số của 4 ký tự khi đọc văn bản Base64, để mỗi chunk chỉ chứa các nhóm 4 ký tự hoàn chỉnh. Ví dụ dùng 4 × 1024 × 192 = 786.432 ký tự (768 KB). Với các file dưới 50 MB, readFile() + Buffer.from(content.trim(), 'base64') đơn giản hơn và đủ nhanh.

Lỗi Thường Gặp

Tôi đã thấy bốn lỗi này trong các codebase JavaScript nhiều lần — chúng thường ẩn khuất cho đến khi một ký tự không phải ASCII hoặc phản hồi API bị ngắt dòng đến được đường dẫn giải mã trong production.

Lỗi 1 — Dùng atob() không có TextDecoder cho nội dung UTF-8

Vấn đề: atob() trả về binary string trong đó mỗi ký tự là một giá trị byte thô. Chuỗi đa byte UTF-8 (Cyrillic, CJK, ký tự có dấu) xuất hiện dưới dạng ký tự Latin-1 bị lộn xộn. Sửa: bọc output trong 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) // Алексей Иванов ✓

Lỗi 2 — Truyền URL-safe Base64 trực tiếp vào atob()

Vấn đề: Các phân đoạn JWT dùng - _ thay vì + /, không có padding. atob() có thể trả về dữ liệu sai hoặc ném lỗi. Sửa: khôi phục ký tự chuẩn và thêm lại padding trước.

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"} ✓

Lỗi 3 — Không loại bỏ newline từ Base64 bị ngắt dòng

Vấn đề: GitHub Contents API và MIME encoder ngắt dòng Base64 output tại 60–76 ký tự mỗi dòng. atob() ném InvalidCharacterError trên các ký tự \n. Sửa: loại bỏ tất cả khoảng trắng trước khi giải mã.

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) // ✓

Lỗi 4 — Gọi .toString() trên nội dung nhị phân đã giải mã

Vấn đề: Khi dữ liệu gốc là nhị phân (ảnh, PDF, audio), gọi .toString('utf8') thay thế các chuỗi byte không nhận ra bằng U+FFFD, làm hỏng output một cách im lặng. Sửa: giữ kết quả dưới dạng Buffer — đừng chuyển sang string.

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

Phương Pháp Giải Mã Base64 JavaScript — So Sánh Nhanh

Phương phápOutput UTF-8Output nhị phânURL-safeMôi trườngCần cài đặt
atob()❌ cần TextDecoder✅ binary string❌ khôi phục thủ côngBrowser, Node 16+, Bun, DenoKhông
TextDecoder + atob()✅ UTF-8✅ qua Uint8Array❌ khôi phục thủ côngBrowser, Node 16+, DenoKhông
Buffer.from().toString()✅ utf8✅ giữ nguyên Buffer✅ base64url (Node 18+)Node.js, BunKhông
Uint8Array.fromBase64() (TC39)✅ qua TextDecoder✅ native✅ tùy chọn alphabetChrome 130+, Node 22+Không
js-base64✅ luôn luôn✅ Uint8Array✅ tích hợp sẵnUniversalnpm install

Chọn atob() chỉ khi nội dung đã giải mã được đảm bảo là văn bản ASCII. Với bất kỳ văn bản do người dùng cung cấp hoặc đa ngôn ngữ trong trình duyệt, hãy dùng TextDecoder + atob(). Với code server-side Node.js, Buffer là lựa chọn mặc định đúng — nó xử lý UTF-8 tự động và giữ nguyên dữ liệu nhị phân. Với các thư viện đa môi trường, js-base64 loại bỏ tất cả các trường hợp ngoại lệ.

Câu Hỏi Thường Gặp

Tại sao atob() trả về ký tự lộn xộn thay vì văn bản có thể đọc?
atob() trả về binary string trong đó mỗi ký tự đại diện cho một byte thô (0–255), không phải code point Unicode. Nếu văn bản gốc được mã hóa UTF-8, bất kỳ ký tự nào trên U+007F — Cyrillic, Arabic, ideograph CJK, chữ có dấu — sẽ xuất hiện dưới dạng hai hoặc nhiều ký tự Latin-1 lộn xộn. Cách sửa: truyền output qua TextDecoder: const bytes = Uint8Array.from(atob(encoded), ch => ch.charCodeAt(0)); const text = new TextDecoder().decode(bytes). Trong Node.js, dùng Buffer.from(encoded, 'base64').toString('utf8') xử lý việc này tự động.
Làm sao giải mã JWT token payload trong JavaScript?
JWT có ba phân đoạn URL-safe Base64 được phân tách bởi dấu chấm: header.payload.signature. Để giải mã payload: const [, payloadB64] = token.split('.'). Trong browser: khôi phục ký tự chuẩn, thêm padding, giải mã với atob() và TextDecoder. Trong Node.js 18+: Buffer.from(payloadB64, 'base64url').toString('utf8'). Quan trọng: giải mã chỉ tiết lộ các claims — nó KHÔNG xác minh chữ ký. Dùng thư viện JWT phù hợp (jsonwebtoken, jose) để giải mã có xác minh trong production.
Sự khác biệt giữa atob() và Buffer.from() để giải mã là gì?
atob() có sẵn trong mọi môi trường JavaScript (browser, Node.js 16+, Bun, Deno) không cần import, nhưng trả về binary string — bạn cần TextDecoder để chuyển đổi nội dung UTF-8 thành văn bản có thể đọc. Buffer.from(encoded, 'base64') chỉ dành cho Node.js / Bun, trả về Buffer thực sự (an toàn nhị phân), xử lý UTF-8 natively, và hỗ trợ 'base64url' trong Node.js 18+. Với code server-side, Buffer đơn giản hơn. Với code browser, atob() + TextDecoder là chuẩn. Với thư viện đa môi trường, js-base64 trừu tượng hóa sự khác biệt.
Làm sao giải mã URL-safe Base64 trong browser?
URL-safe Base64 thay + bằng -, / bằng _, và bỏ padding =. Khôi phục trước khi gọi 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))). Trong Node.js 18+: Buffer.from(input, 'base64url').toString('utf8') xử lý trong một lời gọi.
Làm sao giải mã nội dung Base64 từ GitHub API trong JavaScript?
GitHub Contents API trả về nội dung file dưới dạng Base64 chuẩn với ký tự xuống dòng mỗi 60 ký tự. Loại bỏ trước khi giải mã: const clean = data.content.replace(/\n/g, ''). Trong browser: new TextDecoder().decode(Uint8Array.from(atob(clean), c => c.charCodeAt(0))). Trong Node.js: Buffer.from(clean, 'base64').toString('utf8'). Với file nhị phân (ảnh, PDF), giữ nguyên Buffer không gọi .toString() — truyền trực tiếp vào writeFile hoặc response stream.
Có thể giải mã ảnh được mã hóa Base64 trong JavaScript không cần thư viện không?
Có. Trong browser: 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). Với img src, tạo data URI thay thế: const src = 'data:image/png;base64,' + encoded — điều này bỏ qua hoàn toàn bước giải mã. Trong Node.js: Buffer.from(encoded, 'base64') rồi writeFileSync('./out.png', buffer). Quy tắc chính: không bao giờ gọi .toString() trên Buffer đã giải mã khi nội dung là nhị phân.

Công Cụ Liên Quan

Để giải mã một cú nhấp chuột mà không cần viết code, hãy dán chuỗi Base64 của bạn trực tiếp vào Bộ giải mã Base64 — xử lý cả chế độ chuẩn và URL-safe với kết quả ngay lập tức trong trình duyệt của bạn.

Cũng có sẵn trong: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 LaurentNgười đánh giá kỹ thuật

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.