Enviar um arquivo
  1. base64hub
  2. Base64 no código
  3. Node.js
Código

Base64 em Node.js

O Node.js faz Base64 pelo Buffer, com base64 e base64url como codificações embutidas. Todas as saídas abaixo vêm do Node.js 24.

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ça TypeError [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 um undefined ou um número veio parar ali a partir de um JSON.
  • new Buffer(). O construtor está obsoleto (DEP0005) por motivos de segurança. Use Buffer.from ou Buffer.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 como Buffer.
  • 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 \n e \r ao decodificar, então entrada com quebras MIME funciona, mas o codificador nunca quebra linhas. Use b64.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 btoa e atob: Node.js 16.0, marcados como legados.
  • Uint8Array.prototype.toBase64 e Uint8Array.fromBase64 não estão disponíveis no Node.js 24 (typeof retorna "undefined"). Eles chegam com versões mais novas do V8; até lá, o caminho é o Buffer.
  • 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.

Continue lendo