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.
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:
Bufferhandles 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.Buffersupports streaming and is more memory-efficient for large data.- Use
Bufferfor server-side code. Usebtoa()/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.