O Node.js oferece uma implementação compatível com a API Fetch dos navegadores. Com fetch(), Request, Response, Headers, FormData e Web Streams, aplicações backend podem realizar chamadas HTTP usando um padrão conhecido.
A API é simples, mas produção exige mais que chamar uma URL. É preciso definir timeout, validar status, limitar corpo, proteger redirects, reutilizar conexões, classificar erros e aplicar retries somente quando seguros.
Primeira requisição
const response = await fetch('https://api.example.com/users/42');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const user = await response.json();
console.log(user);Fetch rejeita em falhas de rede, mas não rejeita automaticamente em status 404 ou 500. Sempre verifique response.ok ou response.status.
Headers
const response = await fetch(url, {
headers: {
accept: 'application/json',
'user-agent': 'my-service/1.0',
},
});Não registre Authorization, Cookie ou tokens. Use um cliente por dependência para centralizar headers seguros.
POST com JSON
const response = await fetch('https://api.example.com/orders', {
method: 'POST',
headers: {
'content-type': 'application/json',
accept: 'application/json',
},
body: JSON.stringify({ productId: 42, quantity: 2 }),
});Para operações de criação, use idempotency key quando o serviço suportar. Uma falha de rede pode acontecer depois que o servidor processou a requisição.
Timeout
const response = await fetch(url, {
signal: AbortSignal.timeout(5_000),
});O timeout deve caber no deadline total. Se uma requisição de usuário tem oito segundos, não configure cada dependência com oito segundos em sequência.
Combinando cancelamento
const signal = AbortSignal.any([
requestSignal,
AbortSignal.timeout(3_000),
]);
const response = await fetch(url, { signal });Assim, a chamada termina quando o cliente desconecta ou o prazo local expira.
Consumindo o corpo
O corpo só pode ser consumido uma vez:
const text = await response.text();
Depois disso, chamar json() falha. Escolha o formato com base no Content-Type e no contrato.
Limite de tamanho
response.json() pode acumular um corpo enorme. Para fontes não confiáveis, leia a stream e imponha limite:
async function readLimited(response, maxBytes) {
if (!response.body) return Buffer.alloc(0);
const reader = response.body.getReader();
const chunks = [];
let total = 0;
while (true) {
const { value, done } = await reader.read();
if (done) break;
total += value.byteLength;
if (total > maxBytes) {
await reader.cancel('Resposta excedeu o limite');
throw new Error('Resposta grande demais');
}
chunks.push(value);
}
return Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)));
}Streaming
const response = await fetch(url);
if (!response.ok || !response.body) throw new Error('Falha');
for await (const chunk of response.body) {
await processChunk(chunk);
}Use streaming para arquivos, NDJSON e respostas grandes. Respeite backpressure e cancelamento.
Convertendo para Node Stream
import { Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';
import { createWriteStream } from 'node:fs';
await pipeline(
Readable.fromWeb(response.body),
createWriteStream('download.bin'),
);FormData
const form = new FormData();
form.set('title', 'Documento');
form.set('file', new Blob([buffer]), 'document.bin');
const response = await fetch(url, {
method: 'POST',
body: form,
});Não defina manualmente o boundary do Content-Type. Para arquivos grandes, confirme estratégia de streaming e limite memória.
URLSearchParams
const body = new URLSearchParams({
grant_type: 'client_credentials',
scope: 'read',
});
const response = await fetch(tokenUrl, {
method: 'POST',
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
body,
});Redirects
Fetch pode seguir redirects. Em chamadas sensíveis, use controle:
const response = await fetch(url, {
redirect: 'manual',
});Um redirect pode levar a outro host e vazar credenciais ou atingir rede interna. Revalide cada destino.
SSRF
Nunca busque URL arbitrária sem proteção. Use allowlist, valide protocolo, porta, hostname, IP resolvido e redirects. Bloqueie metadados de cloud e redes privadas quando o recurso for externo.
Autenticação
const response = await fetch(url, {
headers: {
authorization: `Bearer ${token}`,
},
});Armazene tokens em secret manager, não em código. Evite propagar Authorization para redirect em outra origem.
Basic Auth
const credentials = Buffer
.from(`${username}:${password}`)
.toString('base64');
const response = await fetch(url, {
headers: {
authorization: `Basic ${credentials}`,
},
});Basic Auth exige HTTPS e proteção dos segredos.
Cookies
Fetch no backend não possui automaticamente o mesmo cookie jar de um navegador. Se precisar manter sessão, use uma biblioteca de cookie jar ou prefira autenticação por token.
Dispatcher
É possível usar um dispatcher do Undici para controlar pools:
const response = await fetch(url, {
dispatcher: apiPool,
signal,
});Reutilize o dispatcher e feche-o no shutdown.
Retry
Retry deve considerar:
- método idempotente;
- status transitório;
- erro de conexão;
- Retry-After;
- deadline restante;
- backoff com jitter;
- limite de tentativas.
Não repita qualquer POST automaticamente.
Função de cliente
async function fetchJson(url, {
signal,
method = 'GET',
headers,
body,
} = {}) {
const combinedSignal = signal
? AbortSignal.any([signal, AbortSignal.timeout(5_000)])
: AbortSignal.timeout(5_000);
const response = await fetch(url, {
method,
headers,
body,
signal: combinedSignal,
redirect: 'manual',
});
if (!response.ok) {
const errorText = (await response.text()).slice(0, 1000);
throw new Error(`HTTP ${response.status}: ${errorText}`);
}
return response.json();
}Em produção, classifique erros e valide Content-Type.
Request e Response
const request = new Request(url, {
method: 'GET',
headers: { accept: 'application/json' },
});
const response = await fetch(request);Essas classes ajudam em frameworks baseados em padrões web e testes.
Clone
response.clone() permite consumir duas ramificações, mas pode aumentar buffer se uma for lenta. Não use indiscriminadamente em corpos grandes.
Cache
O Fetch do Node.js não deve ser tratado como cache HTTP completo de navegador. Implemente cache na camada adequada ou use CDN, proxy e bibliotecas específicas.
Observabilidade
Meça DNS, conexão, TLS, tempo até headers, duração total, bytes, status, retries, redirects e aborts. Normalize a rota e a dependência para evitar alta cardinalidade.
Logs
Registre método, host conhecido, operação, status e duração. Não registre URL com query sensível, Authorization, Cookie ou corpo completo.
Testes
Use MockAgent do Undici ou servidor local. Teste 200, 404, 500, timeout, corpo inválido, resposta grande, redirect, cancelamento e conexão interrompida.
Erros comuns
- não verificar response.ok;
- não definir timeout;
- usar json em corpo ilimitado;
- seguir redirect arbitrário;
- repetir POST sem idempotência;
- não consumir resposta;
- registrar tokens;
- não propagar cancelamento;
- criar dispatcher por chamada;
- confundir cache do navegador com backend.
Fluxo recomendado
Centralize chamadas por dependência, defina deadline, valide status e Content-Type, limite corpos e proteja redirects. Use Undici no Node.js para controle de pools, AbortController para cancelamento, Circuit Breaker para falhas e DNS no Node.js para resolução.
Consulte a documentação oficial de Fetch no Node.js e a referência da Fetch API.




