Como extrair os bytes de um base64 com gzip dentro de um json e entregar para o navegador
Veja como uma API pode extrair os bytes de uma imagem (ou qualquer outro tipo) que estão comprimidos com Gzip e representados em Base64 dentro de um JSON, e os entrega para o navegador de forma simples e direta.

Uma das formas de representarmos bytes é através do padrão base64. Basicamente o padrão base64 é um sinônimo para um determinado conjunto de bytes, é como se disséssemos "seis" ou "meia dúzia", significa a mesma coisa.

No contexto de APIs Web, o base64 é utilizado devido à sua capacidade de codificar dados binários em um formato de texto. Como muitos protocolos, como HTTP, são projetados para transmitir dados de texto, o base64 facilita essa troca de informações.
Um outro formato, porém de compressão de dados, que é amplamente utilizado é o GZIP. Criado por Jean-loup Gailly e Mark Adler, o GZIP utiliza o algoritmo DEFLATE para compactar arquivos, reduzindo seu tamanho sem perda de dados. Então naturalmente podemos ter bytes comprimidos com o GZIP representados em base64.
Dentro do contexto de APIs, podemos receber um JSON onde um dos campos tenha um valor em base64. Esse valor em base64 pode representar qualquer coisa, uma imagem, texto, audio, ou qualquer outro tipo de arquivo. Inclusive, esses bytes representados em base64 podem estar comprimidos com gzip ou algum outro algoritmo.
{
"imagemEmBase64ComGzip": "conteudo base64",
"outrosCampos": "outros valores"
}
Este conteúdo em Base64 pode ser convertido em bytes e entregue direto ao navegador, sem a necessidade de descomprimir o conteúdo dentro da API, uma vez que o próprio navegador já dá suporte ao gzip. Para fazer isso, basta informar os cabeçalhos content-type
e content-encoding
, onde indicamos o que os bytes representam utilizando um MIME Type e o algoritmo de compressão que foi usado para comprimir os bytes respectivamente.
Neste cenário, vou mostrar como realizar essa tarefa, onde uma API recebe uma imagem cujos bytes estão comprimidos em base64 e entrega essas informações para o frontend de forma simples e transparente.
Exemplo prático
No exemplo a seguir, o navegador faz uma requisição para a API1, que, por sua vez, realiza uma requisição para a API2. O GIF abaixo ilustra esse fluxo:
- Navegador: envia uma requisição para
http://localhost:3001/qrcode
. - API1: recebe a requisição do navegador e faz uma outra requisição para
http://localhost:3000/generate-qrcode?url=https://consolelog.com.br
. - API2: gera o QR Code, comprime os dados utilizando GZIP e retorna a resposta em um JSON para a API1.
- API1: extrai os dados do JSON, converte o conteúdo Base64 (campo
image
) para bytes e entrega ao navegador a imagem resultante. Nos cabeçalhos da resposta, API1 informa que o conteúdo é do tipoimage/png
e está compactado com GZIP.

Código fonte utilizado no cenário de estudo
Siga os seguintes passos para estruturar o cenário de estudo:
# Versão do Node.js utilizada neste exemplo
node --version
# v22.14.0
# Crie o pacote NPM
npm init -y
# Instale as dependências:
npm i express@4 [email protected]
Abaixo deixei o código API2, que gera um QRCode, compacta os bytes da imagem, converte para base64 e então entrega a resposta dentro de um JSON:
const express = require('express');
const QRCode = require('qrcode');
const { gzip } = require('zlib');
const { promisify } = require('util');
const gzipAsync = promisify(gzip);
const app = express();
app.use(express.json());
app.get('/generate-qrcode', async (req, res) => {
console.log('/generate-qrcode');
const url = req.query.url;
if (!url) {
return res.status(400).send('URL not provided');
}
try {
const qrCodeBase64 = await QRCode.toDataURL(url);
const base64Data = qrCodeBase64.replace(
/^data:image\/png;base64,/,
''
);
const buffer = Buffer.from(base64Data, 'base64');
const originalSize = buffer.length;
const compressedBuffer = await gzipAsync(buffer);
const compressedSize = compressedBuffer.length;
const compressionRate = (
(1 - compressedSize / originalSize) *
100
).toFixed(2);
console.log(`QR Code compression stats:
- URL ${url}
- Original size: ${originalSize} bytes
- Compressed size: ${compressedSize} bytes
- Compression rate: ${compressionRate}%`);
return res.json({
image: compressedBuffer.toString('base64'),
originalSize,
compressedSize,
compressionRate,
});
} catch (error) {
console.error('Error processing QR code:', error);
return res
.status(500)
.send('Error processing the QR code');
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
api2.js
Já na API1, observe no código abaixo que o conteúdo recebido no fetch
é um JSON. Dentro do JSON o campo image
contém os bytes comprimidos com GZIP e representados em Base64 de uma imagem (image/png
). Então basta extrair o base64 e entregar os bytes para o navegador indicando os seguintes cabeçalhos:
content-type
- indica o que os bytes recebidos representam. Neste caso, uma imagem.content-encoding
- indica o algoritmo usado na compressão dos dados.
const express = require('express');
const app = express();
app.use(express.json());
app.get('/qrcode', async (_req, res) => {
const qrcodeValue = 'https://consolelog.com.br';
const apiBaseUrl = 'http://localhost:3000';
const apiEndpoint = '/generate-qrcode';
const url = new URL(apiEndpoint, apiBaseUrl);
url.searchParams.append('url', qrcodeValue);
const response = await fetch(url);
const json = await response.json();
const {
image,
originalSize,
compressedSize,
compressionRate,
} = json;
const buffer = Buffer.from(image, 'base64');
res.setHeader('content-type', 'image/png');
res.setHeader('content-encoding', 'gzip');
res.setHeader('content-size', buffer.length);
res.setHeader('x-original-size', originalSize);
res.setHeader('x-compressed-size', compressedSize);
res.setHeader('x-compression-rate', compressionRate);
return res.send(buffer);
});
const PORT = 3001;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
api1.js
Após executar os dois arquivos com os comandos abaixo, é possível consultar o resultado no navegador através do endereço localhost:3001/qrcode, conforme a imagem abaixo ilustra:
node api1.js
node api2.js

Nesse exemplo, API1 recebe uma string Base64 que representa uma imagem. Como sabemos disso, definimos o cabeçalho Content-Type: image/png
. Se fosse um PDF, usaríamos Content-Type: application/pdf
, ou se fosse HTML, Content-Type: text/html
. O navegador interpreta os bytes recebidos com base nesse cabeçalho. Da mesma forma, Content-Encoding
informa se houve compressão e qual algoritmo foi usado.
Considerações
Em resumo, o uso de Base64 e Gzip apresenta uma compensação entre tamanho e eficiência. O Gzip comprime dados, reduzindo o tamanho da transmissão, enquanto o Base64 codifica dados binários em texto, permitindo sua inclusão em formatos textuais como HTML e JSON.
É crucial entender que o Base64 aumenta o tamanho dos dados, o que pode impactar o desempenho. No entanto, quando utilizado em conjunto com o Gzip, pode ser uma estratégia eficaz para otimizar o carregamento de recursos, especialmente em cenários como a incorporação de pequenas imagens diretamente no código.
Um outro ponto relevante, é entender o cabeçalho Content-Type, que informa ao navegador qual é o tipo de dado recebido. Por exemplo, se for Content-Type: application/json, o navegador entende que os dados estão em formato JSON. Se for Content-Type: image/png, ele reconhece os dados como uma imagem PNG. Isso vale para vários outros tipos de arquivos, ajudando o navegador a processá-los corretamente.