Base64 JavaScript — btoa() & Buffer

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

Sử dụng Bộ Mã Hóa 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ử Bộ Mã Hóa Base64 Trực Tuyến trực tuyến →

Khi bạn nhúng ảnh vào CSS data URI, truyền thông tin xác thực trong header HTTP Authorization, hoặc lưu trữ chứng chỉ nhị phân trong biến môi trường, bạn cần mã hóa Base64 dữ liệu JavaScript một cách đáng tin cậy trên cả trình duyệt và Node.js. JavaScript cung cấp hai API tích hợp riêng biệt:btoa() cho môi trường trình duyệt (cũng có trong Node.js 16+) và Buffer.from() cho Node.js — mỗi cái có ràng buộc khác nhau về Unicode, dữ liệu nhị phân và độ an toàn URL. Để mã hóa nhanh mà không cần viết code, Base64 Encoder của ToolDeck xử lý ngay lập tức trong trình duyệt. Hướng dẫn này bao gồm cả hai môi trường với các ví dụ sẵn sàng cho production: xử lý Unicode, biến thể URL-safe, mã hóa tệp và phản hồi API, sử dụng CLI, và bốn lỗi thường xuyên gây ra bug trong codebase thực tế.

  • btoa() có sẵn native trong trình duyệt và toàn cục trong Node.js 16+, nhưng chỉ chấp nhận Latin-1 (code point 0–255) — input Unicode sẽ ném ra DOMException
  • Buffer.from(text, "utf8").toString("base64") là phương thức tương đương trong Node.js, xử lý Unicode natively không cần bước bổ sung
  • Base64 URL-safe thay thế + → -, / → _, và bỏ padding = — dùng Buffer.from().toString("base64url") trong Node.js 18+ để có one-liner
  • Với dữ liệu nhị phân (ArrayBuffer, Uint8Array, tệp), dùng Buffer trong Node.js hoặc cách arrayBuffer() + Uint8Array trong trình duyệt — không bao giờ dùng response.text()
  • Uint8Array.prototype.toBase64() (TC39 Stage 3) đã có trong Node.js 22+ và Chrome 130+, sẽ thống nhất cả hai môi trường

Base64 Encoding là gì?

Base64 chuyển đổi dữ liệu nhị phân tùy ý thành chuỗi được xây dựng từ 64 ký tự ASCII có thể in: A–Z, a–z, 0–9, +, và /. Mỗi 3 byte đầu vào ánh xạ chính xác đến 4 ký tự Base64; nếu độ dài đầu vào không phải là bội số của 3, một hoặc hai ký tự padding = được thêm vào. Đầu ra được mã hóa luôn lớn hơn bản gốc khoảng 33%.

Base64 không phải là mã hóa — nó không cung cấp tính bảo mật. Bất kỳ ai có chuỗi đã mã hóa đều có thể giải mã nó bằng một lần gọi hàm. Mục đích của nó là an toàn truyền tải: nhiều giao thức và định dạng lưu trữ được thiết kế cho văn bản ASCII 7-bit và không thể xử lý byte nhị phân tùy ý. Base64 lấp đầy khoảng cách đó. Các trường hợp sử dụng JavaScript phổ biến bao gồm data URI để nhúng tài sản, header HTTP Basic Auth, các phân đoạn token JWT, tệp đính kèm email MIME, và lưu trữ blob nhị phân trong JSON API.

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

btoa() — Hàm Mã hóa Native của Trình duyệt

btoa() (binary-to-ASCII) đã có trong trình duyệt từ IE10 và trở thành global 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 nhận một đối số chuỗi và trả về dạng được mã hóa Base64. Hàm đối xứng atob() (ASCII-to-binary) giải mã trở lại. Cả hai đều đồng bộ và chạy với bộ nhớ cố định tương đối với kích thước đầu vào.

Ví dụ tối giản hoạt động được

JavaScript (browser / Node.js 16+)
// Encoding an API credential pair for an HTTP Basic Auth header
const serviceId  = 'deploy-bot'
const apiKey     = 'sk-prod-a7f2c91e4b3d8'

const credential = btoa(`${serviceId}:${apiKey}`)
// → 'ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg='

const headers = new Headers({
  Authorization: `Basic ${credential}`,
  'Content-Type': 'application/json',
})

console.log(headers.get('Authorization'))
// Basic ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=

Giải mã với atob()

JavaScript
// Round-trip: encode, transmit, decode
const payload = 'service:payments region:eu-west-1 env:production'

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

console.log(encoded)
// c2VydmljZTpwYXltZW50cyByZWdpb246ZXUtd2VzdC0xIGVudjpwcm9kdWN0aW9u

console.log(decoded === payload) // true
Lưu ý:btoa()atob() là một phần của WinterCG Minimum Common API — spec tương tự đ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.

Xử lý Unicode và Ký tự Non-ASCII

Bẫy phổ biến nhất của btoa() là giới hạn Latin-1 nghiêm ngặt. Bất kỳ ký tự nào có code point trên U+00FF sẽ gây ra ngoại lệ ngay lập tức:

JavaScript
btoa('Müller & Søren') // ❌ Uncaught DOMException: String contains an invalid character
btoa('résumé')         // ❌ 'é' is U+00E9 = 233 — within Latin-1, this one actually works
btoa('田中太郎')         // ❌ Throws — all CJK characters are above U+00FF

Cách tiếp cận đúng là mã hóa chuỗi thành byte UTF-8 trước, sau đó mã hóa Base64 các byte đó. JavaScript cung cấp TextEncoder cho mục đích này:

Cách tiếp cận TextEncoder — an toàn cho mọi đầu vào Unicode

JavaScript (browser + Node.js 16+)
// Utility functions for Unicode-safe Base64
function toBase64(text: string): string {
  const bytes = new TextEncoder().encode(text)
  const chars = Array.from(bytes, byte => String.fromCharCode(byte))
  return btoa(chars.join(''))
}

function fromBase64(encoded: string): string {
  const binary = atob(encoded)
  const bytes  = Uint8Array.from(binary, ch => ch.charCodeAt(0))
  return new TextDecoder().decode(bytes)
}

// Works with any language or script
const orderNote = 'Xác nhận: Nguyễn Văn Nam — kho Hà Nội, số lượng: 250'
const encoded   = toBase64(orderNote)
const decoded   = fromBase64(encoded)

console.log(encoded)
// WMOhYyBuaOG6rW46IE5ndXnhu4VuIFbEg24gTmFtIOKAkyBraG8gSMOgIE7hu5lpLCBz4buRIGzGsOG7o25nOiAyNTA=

console.log(decoded === orderNote) // true
Lưu ý:Nếu bạn đang ở trong Node.js, hãy bỏ qua hoàn toàn giải pháp TextEncoder — dùng Buffer.from(text, 'utf8').toString('base64'). Cách này xử lý Unicode natively và nhanh hơn cho chuỗi lớn.

Buffer.from() trong Node.js — Hướng dẫn đầy đủ với Ví dụ

Trong Node.js, Buffer là API thành ngữ cho mọi thao tác dữ liệu nhị phân, bao gồm chuyển đổi encoding. Nó có trước TextEncoder nhiều năm và vẫn là lựa chọn ưu tiên cho mã phía máy chủ. Ưu điểm chính so với btoa(): hỗ trợ UTF-8 native, xử lý dữ liệu nhị phân, và phím tắt encoding 'base64url' có từ Node.js 18.

Mã hóa và giải mã văn bản cơ bản

Node.js
// Encoding a server configuration object for storage in an env variable
const dbConfig = JSON.stringify({
  host:           'db-primary.internal',
  port:           5432,
  database:       'analytics_prod',
  maxConnections: 100,
  ssl:            { rejectUnauthorized: true },
})

const encoded = Buffer.from(dbConfig, 'utf8').toString('base64')
console.log(encoded)
// eyJob3N0IjoiZGItcHJpbWFyeS5pbnRlcm5hbCIsInBvcnQiOjU0MzIsImRhdGFiYXNlIjoiYW5h...

// Decoding back
const decoded = Buffer.from(encoded, 'base64').toString('utf8')
const config  = JSON.parse(decoded)

console.log(config.host)           // db-primary.internal
console.log(config.maxConnections) // 100

Mã hóa tệp nhị phân từ đĩa

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

// Read a TLS certificate and encode it for embedding in a config file
const certPem     = readFileSync(join(process.cwd(), 'ssl', 'server.crt'))
const certBase64  = certPem.toString('base64')

// Store as a single-line string — suitable for env vars or JSON configs
writeFileSync('./dist/cert.b64', certBase64, 'utf8')

console.log(`Certificate encoded: ${certBase64.length} characters`)
// Certificate encoded: 2856 characters

// Restore the binary cert from the encoded value
const restored = Buffer.from(certBase64, 'base64')
console.log(restored.equals(certPem)) // true

Mã hóa tệp bất đồng bộ với xử lý lỗi

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

async function encodeFileToBase64(filePath: string): Promise<string> {
  try {
    const buffer = await readFile(filePath)
    return buffer.toString('base64')
  } catch (err) {
    const code = (err as NodeJS.ErrnoException).code
    if (code === 'ENOENT') throw new Error(`File not found: ${filePath}`)
    if (code === 'EACCES') throw new Error(`Permission denied: ${filePath}`)
    throw err
  }
}

// Encode a PDF for an email attachment payload
const reportBase64 = await encodeFileToBase64('./reports/q1-financials.pdf')

const emailPayload = {
  to:          'finance-team@company.internal',
  subject:     'Q1 Financial Report',
  attachments: [{
    filename:    'q1-financials.pdf',
    content:     reportBase64,
    encoding:    'base64',
    contentType: 'application/pdf',
  }],
}

console.log(`Attachment: ${reportBase64.length} chars`)

Hàm Base64 JavaScript — Tham chiếu Tham số

Khác với module base64 của Python, JavaScript không có một hàm Base64 thống nhất duy nhất. API phụ thuộc vào môi trường mục tiêu. Đây là tài liệu tham chiếu đầy đủ cho tất cả các cách tiếp cận native:

HàmKiểu đầu vàoUnicodeURL-safeCó sẵn trong
btoa(string)string (Latin-1)❌ ném lỗi trên U+00FF❌ thay thế thủ côngBrowser, Node 16+, Bun, Deno
atob(string)Base64 string❌ trả về chuỗi nhị phân❌ thay thế thủ côngBrowser, Node 16+, Bun, Deno
Buffer.from(src, enc) .toString(enc)string | Buffer | Uint8Array✅ encoding utf8✅ base64url trong Node 18+Node.js, Bun
TextEncoder().encode(str) + btoa()string (bất kỳ Unicode)✅ qua byte UTF-8❌ thay thế thủ côngBrowser, Node 16+, Deno
Uint8Array.toBase64() (TC39)Uint8Array✅ nhị phân✅ omitPadding + alphabetChrome 130+, Node 22+

Chữ ký Buffer.from(src, enc).toString(enc) chấp nhận một số giá trị encoding liên quan đến Base64:

"base64"
Base64 chuẩn (RFC 4648 §4). Dùng + và / với padding =.
"base64url"
Base64 URL-safe (RFC 4648 §5, Node.js 18+). Dùng - và _ không có padding.
"utf8"
Mặc định cho nguồn chuỗi. Dùng khi nguồn là văn bản người đọc được.
"binary"
Latin-1 / ISO-8859-1. Dùng khi nguồn là chuỗi nhị phân thô (ví dụ từ atob()).

Base64 URL-safe — Mã hóa cho JWT, URL, và Tên tệp

Base64 chuẩn dùng + /, vốn là ký tự đặc biệt trong URL — + được giải mã thành dấu cách trong query string, và / là dấu phân cách đường dẫn. JWT, tham số URL, tên tệp, và giá trị cookie đều cần biến thể URL-safe: +-, /_, xóa = ở cuối.

Trình duyệt — thay thế ký tự thủ công

JavaScript (browser)
function toBase64Url(text: string): string {
  // For ASCII-safe input (e.g., JSON with only ASCII chars)
  return btoa(text)
    .replace(/+/g, '-')
    .replace(///g, '_')
    .replace(/=/g, '')
}

function fromBase64Url(encoded: string): string {
  // Restore standard Base64 characters and padding before decoding
  const base64  = encoded.replace(/-/g, '+').replace(/_/g, '/')
  const padded  = base64 + '==='.slice(0, (4 - base64.length % 4) % 4)
  return atob(padded)
}

// JWT header — must be URL-safe Base64
const header  = JSON.stringify({ alg: 'HS256', typ: 'JWT' })
const encoded = toBase64Url(header)
console.log(encoded) // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

const decoded = fromBase64Url(encoded)
console.log(decoded) // {"alg":"HS256","typ":"JWT"}

Node.js 18+ — encoding 'base64url' native

Node.js 18+
// Node.js 18 added 'base64url' as a first-class Buffer encoding
const sessionPayload = JSON.stringify({
  userId:     'usr_9f2a1c3e8b4d',
  role:       'editor',
  workspaceId:'ws_3a7f91c2',
  exp:        Math.floor(Date.now() / 1000) + 3600,
})

const encoded = Buffer.from(sessionPayload, 'utf8').toString('base64url')
// No + or / or = characters in the output
// eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciIsIndvcmtzcGFjZUlkIjoid3NfM2E3ZjkxYzIiLCJleHAiOjE3MTcyMDM2MDB9

const decoded = Buffer.from(encoded, 'base64url').toString('utf8')
console.log(JSON.parse(decoded).role) // editor

Mã hóa Tệp và Phản hồi API trong JavaScript

Trong mã production, mã hóa Base64 thường được áp dụng nhất cho các tệp đang được truyền và phản hồi từ các API bên ngoài cung cấp nội dung nhị phân. Các mẫu khác nhau giữa trình duyệt và Node.js, và dữ liệu nhị phân cần được xử lý cẩn thận.

Trình duyệt — mã hóa tệp từ phần tử input

JavaScript (browser)
// Modern approach: File.arrayBuffer() (Chrome 76+, Firefox 69+, Safari 14+)
async function encodeFile(file: File): Promise<string> {
  const buffer = await file.arrayBuffer()
  const bytes  = new Uint8Array(buffer)
  const chars  = Array.from(bytes, b => String.fromCharCode(b))
  return btoa(chars.join(''))
}

const uploadInput = document.getElementById('avatar') as HTMLInputElement

uploadInput.addEventListener('change', async (e) => {
  const file = (e.target as HTMLInputElement).files?.[0]
  if (!file) return

  try {
    const encoded = await encodeFile(file)
    const dataUri = `data:${file.type};base64,${encoded}`

    // Preview the image inline
    const img   = document.getElementById('preview') as HTMLImageElement
    img.src     = dataUri
    img.hidden  = false

    console.log(`Encoded ${file.name} (${file.size} bytes) → ${encoded.length} Base64 chars`)
  } catch (err) {
    console.error('Encoding failed:', err)
  }
})

Lấy dữ liệu nhị phân mã hóa Base64 từ API

JavaScript
// GitHub Contents API returns file content as Base64 with embedded newlines
async function fetchRepoFile(
  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; size: number }

  if (data.encoding !== 'base64') {
    throw new Error(`Unexpected encoding from GitHub: ${data.encoding}`)
  }

  // GitHub wraps output at 60 chars — strip newlines before decoding
  const clean = data.content.replace(/\n/g, '')
  return atob(clean)
}

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

Khi bạn chỉ cần kiểm tra phản hồi đã mã hóa trong khi debug API mà không cần thiết lập script, hãy dán giá trị Base64 trực tiếp vào Base64 Encoder — nó cũng giải mã, với đầu ra ngay lập tức. Hữu ích để kiểm tra phản hồi GitHub API, payload JWT, và chữ ký webhook.

Mã hóa Base64 từ Command-Line trong Node.js và Shell

Đối với script CI/CD, target Makefile, hoặc debug một lần, bạn hiếm khi cần script đầy đủ. Cả công cụ hệ thống và one-liner Node.js đều bao gồm hầu hết các trường hợp đa nền tảng.

bash
# ── macOS / Linux system base64 ───────────────────────────────────────
# Standard encoding
echo -n "deploy-bot:sk-prod-a7f2c91e4b3d8" | base64
# ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=

# URL-safe variant (replace chars and strip padding)
echo -n "deploy-bot:sk-prod-a7f2c91e4b3d8" | base64 | tr '+/' '-_' | tr -d '='

# Encode a file inline (macOS: -b 0 removes line wrapping; Linux: --wrap=0)
base64 -b 0 ./config/production.json
# or on Linux:
base64 --wrap=0 ./config/production.json

# Decode
echo "ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=" | base64 --decode

# ── Node.js one-liner — works on Windows too ───────────────────────────
node -e "process.stdout.write(Buffer.from(process.argv[1]).toString('base64'))" "my:secret"
# bXk6c2VjcmV0

# URL-safe from Node.js 18+
node -e "process.stdout.write(Buffer.from(process.argv[1]).toString('base64url'))" "my:secret"
# bXk6c2VjcmV0  (same here since there are no special chars)

# Decode in Node.js
node -e "console.log(Buffer.from(process.argv[1], 'base64').toString())" "ZGVwbG95LWJvdA=="
Lưu ý:Trên macOS, base64 mặc định ngắt dòng đầu ra ở 76 ký tự. Điều này phá vỡ quá trình phân tích xuôi dòng. Luôn thêm -b 0 (macOS) hoặc --wrap=0 (Linux) khi bạn cần kết quả một dòng — ví dụ khi ghi vào biến môi trường hoặc trường cấu hình.

Giải pháp thay thế Hiệu suất cao: js-base64

Các API tích hợp đã đủ cho hầu hết các trường hợp sử dụng. Lý do chính để dùng thư viện là tính nhất quán xuyên môi trường: nếu bạn ship một package chạy trong cả trình duyệt và Node.js, việc dùng Buffer yêu cầu phát hiện môi trường hoặc cấu hình bundler, trong khi btoa() cần giải pháp Unicode. js-base64 (100M+ lượt tải xuống hàng tuần trên npm) xử lý cả hai một cách trong suốt.

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

// Standard encoding — Unicode-safe, works in browser and Node.js
const telemetryEvent = JSON.stringify({
  eventId:   'evt_7c3a9f1b2d',
  type:      'checkout_completed',
  currency:  'EUR',
  amount:    14900,
  userId:    'usr_4e2b8d6a5c',
  timestamp: 1717200000,
})

const encoded    = toBase64(telemetryEvent)
const urlEncoded = toBase64Url(telemetryEvent) // No +, /, or = characters

const decoded = fromBase64(encoded)
console.log(JSON.parse(decoded).type) // checkout_completed

// Binary data — pass a Uint8Array as second argument
const pngMagicBytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
const binaryEncoded = toBase64(pngMagicBytes, true) // true = binary mode

// Validation before decoding
const suspicious = 'not!valid@base64#'
console.log(isValid(suspicious)) // false

Bên dưới, js-base64 dùng Buffer native khi có sẵn và fallback về implementation pure-JS trong trình duyệt. Nó nhanh hơn 2–3× so với cách tiếp cận TextEncoder+btoa cho chuỗi Unicode lớn, và API đối xứng ( toBase64 / fromBase64) loại bỏ gánh nặng tinh thần khi nhớ chiều của btoa atob.

Mã hóa Tệp Nhị phân Lớn với Node.js Streams

Khi bạn cần mã hóa các tệp lớn hơn ~50 MB, việc tải toàn bộ tệp vào bộ nhớ bằng readFileSync() trở thành vấn đề. Stream Node.js cho phép bạn xử lý dữ liệu theo từng chunk — nhưng mã hóa Base64 có một ràng buộc: bạn phải cung cấp dữ liệu cho encoder theo bội số 3 byte để tránh padding sai ở ranh giới chunk.

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

// Stream a large binary file to a Base64-encoded output file
async function streamEncodeToBase64(
  inputPath:  string,
  outputPath: string,
): Promise<void> {
  const readStream  = createReadStream(inputPath, { highWaterMark: 3 * 1024 * 256 }) // 768 KB chunks (multiple of 3)
  const writeStream = createWriteStream(outputPath, { encoding: 'utf8' })

  let buffer = Buffer.alloc(0)

  await pipeline(
    readStream,
    async function* (source) {
      for await (const chunk of source) {
        buffer = Buffer.concat([buffer, chunk as Buffer])

        // Encode in complete 3-byte groups to avoid mid-stream padding
        const remainder = buffer.length % 3
        const safe      = buffer.subarray(0, buffer.length - remainder)
        buffer          = buffer.subarray(buffer.length - remainder)

        if (safe.length > 0) yield safe.toString('base64')
      }
      // Flush remaining bytes (may add 1 or 2 '=' padding chars)
      if (buffer.length > 0) yield buffer.toString('base64')
    },
    writeStream,
  )
}

// Usage: encode a 200 MB video attachment
await streamEncodeToBase64(
  './uploads/product-demo.mp4',
  './dist/product-demo.b64',
)
console.log('Stream encoding complete')
Lưu ý:Kích thước chunk phải là bội số 3 byte để tránh padding = giả ở giữa đầu ra. Ví dụ sử dụng 3 * 1024 * 256 = 786.432 byte (768 KB) — điều chỉnh highWaterMark dựa trên ngân sách bộ nhớ của bạn. Với các tệp dưới 50 MB, readFile() + Buffer.toString('base64') đơn giản hơn và đủ nhanh.

Các Lỗi Thường Gặp

Tôi đã xem xét nhiều codebase JavaScript với mã hóa Base64, và bốn lỗi này xuất hiện liên tục — thường không được phát hiện cho đến khi ký tự non-ASCII hoặc tệp nhị phân đến đường dẫn mã hóa trong môi trường production.

Lỗi 1 — Truyền Unicode trực tiếp vào btoa()

Vấn đề: btoa() chỉ chấp nhận ký tự có code point 0–255. Các ký tự như ñ, emoji, hoặc ký tự tượng hình CJK gây ra DOMException ngay lập tức. Giải pháp: mã hóa với TextEncoder trước, hoặc dùng Buffer.from(text, 'utf8').toString('base64') trong Node.js.

Before · JavaScript
After · JavaScript
// ❌ DOMException: The string to be encoded contains
//    characters outside of the Latin1 range
const username = 'Trần Thị Mai'
const encoded  = btoa(username)  // throws
// ✅ Encode as UTF-8 bytes first
function safeEncode(text: string): string {
  const bytes = new TextEncoder().encode(text)
  const chars = Array.from(bytes, b => String.fromCharCode(b))
  return btoa(chars.join(''))
}
const encoded = safeEncode('Trần Thị Mai')
// VHLhuqduIFRo4buLIE1haQ==

Lỗi 2 — Quên khôi phục padding trước atob()

Vấn đề: Base64 URL-safe xóa padding =. Truyền chuỗi đã xóa padding trực tiếp vào atob() tạo ra đầu ra sai hoặc ném lỗi tùy thuộc vào độ dài chuỗi. Giải pháp: khôi phục + / và thêm lại lượng padding chính xác trước khi gọi atob().

Before · JavaScript
After · JavaScript
// ❌ atob() may return wrong data or throw
//    on URL-safe Base64 without padding
const jwtSegment = 'eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ'
const decoded    = atob(jwtSegment) // Unreliable
// ✅ Restore characters 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)
  return atob(pad)
}
const decoded = decodeBase64Url('eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ')
// {"userId":"usr_9f2a1c3e"}

Lỗi 3 — Ghép nối các chunk đã mã hóa thay vì buffer thô

Vấn đề: Mỗi lần gọi btoa() hoặc .toString('base64') thêm padding riêng. Ghép nối hai chuỗi Base64 đã có padding tạo ra đầu ra không hợp lệ vì padding chỉ nên ở cuối cùng. Giải pháp: ghép nối dữ liệu thô trước khi mã hóa.

Before · JavaScript
After · JavaScript
// ❌ Both parts are padded independently —
//    the combined string is not valid Base64
const part1 = Buffer.from('webhook-secret').toString('base64')
// d2ViaG9vay1zZWNyZXQ=  ← has padding
const part2 = Buffer.from('-v2').toString('base64')
// LXYy            ← correct in isolation
const combined = part1 + part2 // ❌ Invalid — padding in the middle
// ✅ Concatenate raw Buffers before encoding
const combined = Buffer.concat([
  Buffer.from('webhook-secret'),
  Buffer.from('-v2'),
]).toString('base64')
// d2ViaG9vay1zZWNyZXQtdjI= — single valid Base64 string

Lỗi 4 — Dùng response.text() để đọc dữ liệu API nhị phân trước khi mã hóa

Vấn đề: response.text() giải thích các byte thô là UTF-8 và thay thế các chuỗi byte không được nhận dạng bằng ký tự thay thế U+FFFD. Bất kỳ nội dung nhị phân nào — hình ảnh, PDF, âm thanh — bị hỏng âm thầm trước khi đến btoa(). Giải pháp: dùng response.arrayBuffer() để lấy byte thô.

Before · JavaScript
After · JavaScript
// ❌ response.text() corrupts binary data
const res     = await fetch('/api/exports/invoice.pdf')
const text    = await res.text()   // ❌ PDF bytes mangled as UTF-8
const encoded = btoa(text)         // ❌ Corrupted Base64
// ✅ arrayBuffer() preserves raw bytes
const res     = await fetch('/api/exports/invoice.pdf')
const buffer  = await res.arrayBuffer()
const bytes   = new Uint8Array(buffer)
const chars   = Array.from(bytes, b => String.fromCharCode(b))
const encoded = btoa(chars.join('')) // ✅ Valid Base64

Các Phương thức Base64 JavaScript — So sánh Nhanh

Phương thứcUnicodeDữ liệu nhị phânURL-safeMôi trườngCần cài đặt
btoa() / atob()❌ Latin-1❌ cần giải pháp thay thế❌ thay thế thủ côngBrowser, Node 16+, Bun, DenoKhông
TextEncoder + btoa()✅ UTF-8✅ qua Uint8Array❌ thay thế thủ côngBrowser, Node 16+, DenoKhông
Buffer.from().toString()✅ utf8✅ native✅ base64url (Node 18+)Node.js, BunKhông
Uint8Array.toBase64() (TC39)✅ nhị phân✅ native✅ tùy chọn alphabetChrome 130+, Node 22+Không
js-base64✅ luôn✅ Uint8Array✅ tích hợp sẵnĐa năngnpm install

Chọn btoa() chỉ khi đầu vào được xác minh là chỉ ASCII — hex digest, ID số, hoặc chuỗi Latin-1 đã được xác thực trước. Với văn bản người dùng cung cấp trong trình duyệt, dùng TextEncoder + btoa(). Với toàn bộ mã Node.js phía máy chủ, Buffer là mặc định đúng. Với thư viện cần chạy trong cả hai môi trường mà không cần cấu hình bundler, js-base64 loại bỏ tất cả các trường hợp biên.

Câu hỏi Thường gặp

Tại sao btoa() ném "InvalidCharacterError" với chuỗi của tôi?
btoa() chỉ chấp nhận ký tự có code point trong khoảng 0–255 (Latin-1 / ISO-8859-1). Bất kỳ ký tự nào trên U+00FF — bao gồm hầu hết Cyrillic, Ả Rập, ký tự tượng hình CJK, và nhiều emoji — gây ra DOMException. Giải pháp phụ thuộc vào môi trường: trong trình duyệt, mã hóa thành byte UTF-8 với TextEncoder trước, chuyển mỗi byte thành ký tự với String.fromCharCode(), sau đó gọi btoa(). Trong Node.js, dùng Buffer.from(text, 'utf8').toString('base64') xử lý Unicode natively.
btoa() có sẵn trong Node.js mà không cần import không?
Có, từ Node.js 16.0. Cả btoa() và atob() đều được đăng ký là hàm global — không cần import. Chúng hoạt động giống hệt với các phiên bản trình duyệt, kể cả giới hạn Latin-1. Với mã server Node.js, Buffer.from() vẫn được ưu tiên hơn btoa() vì nó xử lý UTF-8 natively, hỗ trợ dữ liệu nhị phân không cần giải pháp thay thế, và có tùy chọn encoding 'base64url' được thêm vào Node.js 18.
Sự khác biệt giữa Base64 chuẩn và Base64 URL-safe là gì?
Base64 chuẩn (RFC 4648 §4) dùng + cho giá trị 62, / cho giá trị 63, và = cho padding. Những ký tự này có ý nghĩa đặc biệt trong URL: + được giải thích là khoảng trắng trong query string, và / là dấu phân cách đường dẫn. Base64 URL-safe (RFC 4648 §5) thay thế - cho + và _ cho /, và thường bỏ hoàn toàn padding =. JWT dùng Base64 URL-safe cho cả ba phân đoạn. Trong Node.js 18+, Buffer.from(text).toString('base64url') tạo ra định dạng URL-safe trực tiếp.
Làm thế nào để mã hóa ảnh thành Base64 cho CSS data URI trong JavaScript?
Trong trình duyệt: dùng file.arrayBuffer() để đọc nhị phân, chuyển thành Uint8Array, sau đó gọi btoa(Array.from(bytes, b => String.fromCharCode(b)).join('')). Xây dựng data URI là 'data:' + file.type + ';base64,' + encoded. Trong Node.js: const encoded = fs.readFileSync('./image.png').toString('base64') và thêm loại MIME vào trước. Với tệp SVG thường có thể bỏ qua hoàn toàn Base64 và dùng data URI được URL-encode thay thế, dễ đọc hơn và nhỏ hơn một chút.
Tôi có thể mã hóa và giải mã Base64 mà không cần thư viện npm trong trình duyệt không?
Có. Với đầu vào chỉ ASCII, btoa() và atob() hoạt động trực tiếp. Với Unicode, cặp TextEncoder / TextDecoder cung cấp cho bạn bộ công cụ đầy đủ — cả hai đều được tích hợp trong tất cả trình duyệt hiện đại và Node.js 16+. Trường hợp duy nhất thư viện thực sự thêm giá trị là tính nhất quán xuyên môi trường: nếu bạn viết tiện ích phải hoạt động giống hệt trong cả trình duyệt và Node.js mà không cần cấu hình bundler, js-base64 loại bỏ logic phát hiện môi trường.
Làm thế nào để giải mã nội dung Base64 từ GitHub API?
GitHub Contents API trả về nội dung tệp dưới dạng Base64 với các ký tự xuống dòng nhúng (API ngắt dòng đầu ra ở 60 ký tự). Xóa chúng trước khi giải mã: const clean = data.content.replace(/\n/g, ''); const text = atob(clean);. Trong Node.js: const text = Buffer.from(data.content.replace(/\n/g, ''), 'base64').toString('utf8');. GitHub luôn dùng Base64 chuẩn (không phải URL-safe), nên không cần thay thế + → - hoặc / → _.

Công cụ Liên quan

Để mã hóa hoặc giải mã một cú click mà không cần viết code, dán chuỗi hoặc dữ liệu nhị phân của bạn trực tiếp vào Base64 Encoder — xử lý cả chế độ chuẩn và URL-safe ngay lập tức trong trình duyệt của bạn.

Cũng có sẵn trong:PythonJava
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.