Base64 with Node.js Buffer

Master Base64 encoding and decoding in Node.js using Buffer, streams, and the global btoa/atob functions. Covers files, strings, and performance tips.

Language

Detailed Explanation

Node.js provides robust Base64 support through its Buffer class, which has been available since the earliest versions. Starting with Node.js 16, the global btoa() and atob() functions are also available, but Buffer remains the preferred approach for server-side code.

Basic encoding and decoding with Buffer:

// String to Base64
const encoded = Buffer.from("Hello, World!").toString("base64");
// "SGVsbG8sIFdvcmxkIQ=="

// Base64 to string
const decoded = Buffer.from("SGVsbG8sIFdvcmxkIQ==", "base64").toString("utf-8");
// "Hello, World!"

URL-safe Base64:

// Encode as Base64url
const urlSafe = Buffer.from("Hello, World!").toString("base64url");
// "SGVsbG8sIFdvcmxkIQ"  (no padding, - and _ instead of + and /)

// Decode Base64url
const fromUrlSafe = Buffer.from("SGVsbG8sIFdvcmxkIQ", "base64url").toString("utf-8");

The "base64url" encoding option was added in Node.js 14 and handles the character substitution and padding removal automatically.

File encoding and decoding:

const fs = require("fs");

// File to Base64
const fileBase64 = fs.readFileSync("image.png").toString("base64");

// Base64 to file
fs.writeFileSync("output.png", Buffer.from(fileBase64, "base64"));

Streaming for large files: For files too large to load into memory, use streams with the built-in transform:

const { Transform } = require("stream");
const fs = require("fs");

// Custom Base64 encoding stream
class Base64Encode extends Transform {
  constructor() {
    super();
    this.remainder = Buffer.alloc(0);
  }
  _transform(chunk, encoding, callback) {
    const combined = Buffer.concat([this.remainder, chunk]);
    const usable = combined.length - (combined.length % 3);
    this.push(combined.slice(0, usable).toString("base64"));
    this.remainder = combined.slice(usable);
    callback();
  }
  _flush(callback) {
    if (this.remainder.length > 0) {
      this.push(this.remainder.toString("base64"));
    }
    callback();
  }
}

fs.createReadStream("large-file.bin")
  .pipe(new Base64Encode())
  .pipe(fs.createWriteStream("output.b64"));

Buffer vs btoa/atob:

  • Buffer handles binary data natively and supports multiple encodings (utf-8, base64, base64url, hex, latin1).
  • btoa()/atob() only handle Latin-1 strings, just like in browsers. They throw on characters above code point 255.
  • Buffer supports streaming and is more memory-efficient for large data.
  • Use Buffer for server-side code. Use btoa()/atob() only when writing isomorphic code that must match browser behavior.

Common mistake: Using Buffer.from(string) without specifying the encoding when decoding. The default encoding is "utf-8", which interprets the input as a UTF-8 string rather than Base64 data. Always specify "base64" as the second argument when decoding.

Use Case

Building a Node.js microservice that receives file uploads as Base64-encoded JSON payloads, decodes them using Buffer, and streams them to cloud storage.

Try It — Base64 Encoder

Open full tool