Codificar e decodificar uma string
Buffer.from(string, "utf8") transforma o texto em bytes UTF-8, e .toString("base64") os codifica. Para decodificar, são as mesmas duas chamadas na ordem inversa.
const enc = Buffer.from("héllo wörld", "utf8").toString("base64");
console.log(enc); // aMOpbGxvIHfDtnJsZA==
console.log(Buffer.from(enc, "base64").toString("utf8")); // héllo wörld
"utf8" é o padrão nas duas chamadas, mas escrevê-lo deixa a intenção clara. O Buffer é uma subclasse de Uint8Array, então um Uint8Array simples precisa ser envolvido: u8.toString("base64") ignora o argumento e retorna "104,105", enquanto Buffer.from(u8).toString("base64") retorna aGk=.
Base64 URL-safe (base64url)
O Node tem uma codificação dedicada ao alfabeto URL-safe. Ela nunca escreve preenchimento:
Buffer.from("<<???>>").toString("base64"); // PDw/Pz8+Pg==
Buffer.from("<<???>>").toString("base64url"); // PDw_Pz8-Pg
Buffer.from("PDw_Pz8-Pg", "base64url").toString(); // <<???>>
Se quem consome espera Base64 URL-safe com preenchimento, acrescente-o você mesmo: s + "=".repeat((4 - s.length % 4) % 4).
Na decodificação, o Node é tolerante: tanto "base64" quanto "base64url" aceitam os dois alfabetos e não se importam com o preenchimento.
Buffer.from("PDw_Pz8-Pg==", "base64").toString(); // <<???>>
Buffer.from("PDw/Pz8+Pg==", "base64url").toString(); // <<???>>
Isso é prático para JWTs e tokens (experimente o decodificador de JWT), mas veja a seção de validação abaixo. Contexto sobre os dois alfabetos: Base64 vs Base64URL.
Codificar e decodificar um arquivo
O fs aceita "base64" como codificação, o que economiza um passo nas duas direções:
import { readFileSync, writeFileSync } from "node:fs";
readFileSync("hello.txt").toString("base64"); // aGVsbG8K
readFileSync("hello.txt", "base64"); // aGVsbG8K (same thing)
writeFileSync("copy.txt", Buffer.from("aGVsbG8K", "base64"));
writeFileSync("copy2.txt", "aGVsbG8K", "base64"); // decodes while writing
Para embutir um arquivo em HTML ou CSS, acrescente o prefixo ao resultado: `data:image/png;base64,${b64}`. O gerador de data URI monta a mesma string no navegador.
Streaming de arquivos grandes
Ler um arquivo de 2 GB para a memória só para codificá-lo é má ideia. Um stream Transform pode codificar bloco a bloco, desde que só codifique grupos inteiros de 3 bytes e leve o resto para o bloco seguinte:
import { createReadStream, createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
import { Transform } from "node:stream";
class Base64Encode extends Transform {
constructor() { super(); this.rest = Buffer.alloc(0); }
_transform(chunk, _enc, cb) {
const buf = Buffer.concat([this.rest, chunk]);
const cut = buf.length - (buf.length % 3);
this.rest = buf.subarray(cut);
cb(null, buf.subarray(0, cut).toString("base64"));
}
_flush(cb) { cb(null, this.rest.toString("base64")); }
}
await pipeline(createReadStream("big.bin"), new Base64Encode(),
createWriteStream("big.b64"));
Num arquivo aleatório de 1 MB, a saída bateu exatamente com readFileSync("big.bin").toString("base64"). Sem levar o resto adiante, cada fronteira de bloco que não fosse múltiplo de 3 inseriria preenchimento = no meio da saída. O motivo é explicado em como o Base64 funciona.
Para saber o tamanho decodificado sem decodificar, use Buffer.byteLength("aGVsbG8K", "base64"), que retorna 6.
Validar a entrada
Buffer.from(s, "base64") nunca lança erro por conteúdo inválido. Ele descarta os caracteres que não estão em nenhum dos alfabetos e retorna o que sobrou:
Buffer.from("aGk=!!", "base64"); // <Buffer 68 69>
Buffer.from("not base64 at all", "base64"); // <Buffer 9e 8b 5b 6a c7 ba e1 ab 5a 96>
Buffer.from("aGk", "base64").toString(); // hi (missing padding is fine)
Para entrada não confiável, confira o alfabeto e o comprimento, depois recodifique e compare. A ida e volta pega bits de preenchimento diferentes de zero, como em aGl=, que também decodifica para hi, mas não é canônico.
function isBase64(s) {
return /^[A-Za-z0-9+/]*={0,2}$/.test(s) && s.length % 4 === 0 &&
Buffer.from(s, "base64").toString("base64") === s;
}
isBase64("aGk="); // true
isBase64("aGk"); // false (no padding)
isBase64("aGl="); // false (non-canonical)
isBase64("not base64"); // false
Para base64url, ajuste a regex para [A-Za-z0-9_-] e remova a verificação de comprimento. O validador de Base64 faz as mesmas verificações de forma interativa.
Armadilhas e mensagens de erro
- Tipo de argumento errado.
Buffer.from(123, "base64")lançaTypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received type number (123). Geralmente umundefinedou um número veio parar ali a partir de um JSON. new Buffer(). O construtor está obsoleto (DEP0005) por motivos de segurança. UseBuffer.fromouBuffer.alloc.- Decodificar binário como texto.
.toString("utf8")em bytes de imagem gera caracteres de substituição (U+FFFD) sem erro, e o arquivo fica corrompido quando é gravado de volta. Mantenha dados binários comoBuffer. - Prefixo de data URI.
Buffer.from("data:image/png;base64,iVBOR...", "base64")não falha; ele decodifica o prefixo como lixo. Divida na vírgula antes. - Quebras de linha. O Node ignora
\ne\rao decodificar, então entrada com quebras MIME funciona, mas o codificador nunca quebra linhas. Useb64.match(/.{1,76}/g).join("\r\n")quando precisar de linhas MIME.
Notas de versão
- Codificação
base64url: Node.js 15.7.0 e 14.18.0. - Globais
btoaeatob: Node.js 16.0, marcados como legados. Uint8Array.prototype.toBase64eUint8Array.fromBase64não estão disponíveis no Node.js 24 (typeofretorna"undefined"). Eles chegam com versões mais novas do V8; até lá, o caminho é oBuffer.Buffer.from(string, "base64")ignorou espaços em branco e caracteres inválidos em todas as versões, então não conte com ele para validar.
Perguntas frequentes
Buffer.from(str, 'base64') lança erro com entrada inválida?
Não. O Node pula os caracteres fora do alfabeto e para no primeiro =, então lixo é decodificado em alguns bytes sem erro nenhum. Valide a string você mesmo se a entrada não for confiável.
Em qual versão do Node.js entrou o base64url?
A codificação base64url chegou no Node.js 15.7.0 e foi portada para o 14.18.0. Em qualquer versão atual, você pode contar com ela.
Devo usar btoa no Node.js?
O Node marca btoa e atob como legados e recomenda o Buffer. Eles existem por compatibilidade com o navegador e têm a mesma limitação ao Latin-1 descrita em Base64 em JavaScript.