X Xerobit

Base64 in Node.js — Buffer, btoa, and Streaming Large Files

Node.js provides Buffer for Base64 encoding/decoding and native btoa/atob since v16. Learn how to encode strings, binary files, streams, and JSON payloads, handle URL-safe...

Mian Ali Khalid · · 4 min read
Use the tool
Base64 Encoder / Decoder
Encode and decode Base64 strings and files. Client-side, safe for sensitive data.
Open Base64 Encoder / Decoder →

Node.js has two ways to handle Base64: the Buffer class (Node-native, works everywhere) and btoa/atob (Web API, added in Node 16). Use Buffer for binary data; btoa only handles strings.

Use the base64 decoder and encoder to encode and decode online.

Buffer — the Node.js way

// Encode string to Base64:
const encoded = Buffer.from('Hello World').toString('base64');
// 'SGVsbG8gV29ybGQ='

// Decode Base64 to string:
const decoded = Buffer.from('SGVsbG8gV29ybGQ=', 'base64').toString('utf8');
// 'Hello World'

// Encode binary data:
const bytes = new Uint8Array([72, 101, 108, 108, 111]);
const encoded = Buffer.from(bytes).toString('base64');
// 'SGVsbG8='

// Encode JSON:
const payload = { user: 'alice', role: 'admin' };
const encodedJson = Buffer.from(JSON.stringify(payload)).toString('base64');

// Decode JSON:
const raw = Buffer.from(encodedJson, 'base64').toString('utf8');
const data = JSON.parse(raw);

btoa / atob (Node 16+)

// btoa: string to Base64
const encoded = btoa('Hello World');
// 'SGVsbG8gV29ybGQ='

// atob: Base64 to string
const decoded = atob('SGVsbG8gV29ybGQ=');
// 'Hello World'

// Limitation: btoa only handles Latin-1 (bytes 0-255)
// For Unicode strings, use Buffer:
try {
  btoa('Hello 🌍');  // Throws: character out of range!
} catch {}

// Correct Unicode handling:
const encoded = Buffer.from('Hello 🌍').toString('base64');
const decoded = Buffer.from(encoded, 'base64').toString('utf8');

Encode a file to Base64

import { readFile } from 'fs/promises';

async function fileToBase64(filepath) {
  const buffer = await readFile(filepath);
  return buffer.toString('base64');
}

// Usage:
const b64 = await fileToBase64('./image.png');
const dataUrl = `data:image/png;base64,${b64}`;

Stream large files (avoid memory issues)

For files > 50MB, streaming is more memory-efficient:

import { createReadStream, createWriteStream } from 'fs';
import { Transform } from 'stream';

function base64EncodeStream(inputPath, outputPath) {
  return new Promise((resolve, reject) => {
    const input = createReadStream(inputPath);
    const output = createWriteStream(outputPath);
    
    // Transform each chunk to base64:
    const base64Transform = new Transform({
      transform(chunk, encoding, callback) {
        callback(null, chunk.toString('base64'));
      },
    });
    
    input
      .pipe(base64Transform)
      .pipe(output)
      .on('finish', resolve)
      .on('error', reject);
  });
}

// Decode stream:
function base64DecodeStream(inputPath, outputPath) {
  return new Promise((resolve, reject) => {
    const input = createReadStream(inputPath);
    const output = createWriteStream(outputPath);
    
    // Decode each chunk: must handle chunk boundaries carefully
    let leftover = '';
    const decoder = new Transform({
      transform(chunk, encoding, callback) {
        const text = leftover + chunk.toString('ascii');
        const remainder = text.length % 4;
        leftover = text.slice(text.length - remainder);
        const toProcess = text.slice(0, text.length - remainder);
        if (toProcess) {
          callback(null, Buffer.from(toProcess, 'base64'));
        } else {
          callback();
        }
      },
      flush(callback) {
        if (leftover) callback(null, Buffer.from(leftover, 'base64'));
        else callback();
      },
    });
    
    input.pipe(decoder).pipe(output).on('finish', resolve).on('error', reject);
  });
}

URL-safe Base64 in Node.js

// Standard Base64: uses +, /, and = padding
// URL-safe Base64: uses -, _ and no padding

function toBase64Url(input) {
  return Buffer.from(input)
    .toString('base64')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '');  // Remove padding
}

function fromBase64Url(encoded) {
  // Add padding back:
  const padded = encoded + '==='.slice(0, (4 - encoded.length % 4) % 4);
  return Buffer.from(padded.replace(/-/g, '+').replace(/_/g, '/'), 'base64');
}

// JWT uses URL-safe Base64 for all three parts
toBase64Url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))
// 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'

HTTP Basic Auth

// Create Authorization header:
function basicAuthHeader(username, password) {
  return `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
}

// Parse Authorization header:
function parseBasicAuth(header) {
  if (!header?.startsWith('Basic ')) return null;
  const decoded = Buffer.from(header.slice(6), 'base64').toString('utf8');
  const colon = decoded.indexOf(':');
  if (colon < 0) return null;
  return {
    username: decoded.slice(0, colon),
    password: decoded.slice(colon + 1),
  };
}

Related posts

Related tool

Base64 Encoder / Decoder

Encode and decode Base64 strings and files. Client-side, safe for sensitive data.

Written by Mian Ali Khalid. Part of the Dev Productivity pillar.