O Fetch Nativo no Node.js permite fazer requisições HTTP usando a mesma interface conhecida dos navegadores. Com fetch(), Request, Response, Headers, FormData e Web Streams, aplicações backend conseguem consumir APIs sem instalar uma biblioteca apenas para tarefas básicas.
A implementação do Node.js é baseada no projeto Undici e possui diferenças importantes em relação ao navegador. Não existe política de CORS aplicada pelo cliente, cookies não são armazenados automaticamente e opções de conexão usam dispatchers, não o http.Agent tradicional.
Neste guia, você aprenderá a fazer GET e POST, enviar JSON, configurar headers, ler respostas, usar streaming, timeouts, AbortController, retries, autenticação, uploads e observabilidade.
O que é o fetch nativo?
fetch() é uma API baseada em Promises para enviar requisições e receber uma Response. A documentação oficial do fetch no Node.js descreve a API global. A documentação da Fetch API na MDN explica Request, Response e Headers.
Para URLs, consulte URL API no Node.js. Para cancelamento, veja AbortController no Node.js. O artigo Web Streams API no Node.js ajuda no processamento de corpos grandes.
GET básico
const response = await fetch('https://api.example.com/users');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const users = await response.json();O fetch só rejeita automaticamente em falhas de rede ou cancelamento. Respostas 404 e 500 resolvem normalmente e precisam ser verificadas com response.ok ou status.
Headers
const response = await fetch(url, {
headers: {
accept: 'application/json',
'user-agent': 'my-service/1.0'
}
});Evite incluir tokens em logs. Use nomes de aplicação e versão no User-Agent quando o provedor permitir.
POST com JSON
const response = await fetch(url, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
name: 'Ana',
active: true
})
});Definir o body não configura automaticamente o Content-Type para JSON. Valide também o tipo da resposta antes de chamar json().
Lendo texto e bytes
const text = await response.text();
const bytes = await response.arrayBuffer();Esses métodos acumulam todo o corpo em memória. Para downloads grandes, use o stream da resposta.
Streaming da resposta
const response = await fetch(url);
for await (const chunk of response.body) {
await processChunk(chunk);
}O body é uma ReadableStream. Processar incrementalmente reduz pico de memória, mas cada chunk e o total ainda precisam de limites.
Salvando arquivo
const fs = require('node:fs');
const { Readable } = require('node:stream');
const { pipeline } = require('node:stream/promises');
const response = await fetch(fileUrl);
if (!response.ok) {
throw new Error(`Download falhou: ${response.status}`);
}
await pipeline(
Readable.fromWeb(response.body),
fs.createWriteStream(destination)
);Use diretório controlado e nome gerado pela aplicação. Não transforme URL em caminho local sem validação.
Timeout com AbortSignal
const response = await fetch(url, {
signal: AbortSignal.timeout(5000)
});O suporte depende da versão. Em versões compatíveis, o sinal cancela quando o prazo termina.
AbortController manual
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);
try {
return await fetch(url, {
signal: controller.signal
});
} finally {
clearTimeout(timer);
}Cancelar a Promise ajuda a liberar recursos, mas o serviço remoto pode já ter processado a operação.
Combinação de sinais
Em versões compatíveis, AbortSignal.any() combina timeout e cancelamento do chamador:
const signal = AbortSignal.any([
callerSignal,
AbortSignal.timeout(5000)
]);Query string
const url = new URL('https://api.example.com/products');
url.searchParams.set('page', '2');
url.searchParams.set('limit', '20');
const response = await fetch(url);Não concatene valores manualmente. Consulte Query String no Node.js.
Autenticação Bearer
const response = await fetch(url, {
headers: {
authorization: `Bearer ${token}`
}
});Carregue o token de um gerenciador de segredos e nunca inclua o header completo em mensagens de erro.
Basic Auth
const credentials = Buffer
.from(`${username}:${password}`)
.toString('base64');
const response = await fetch(url, {
headers: {
authorization: `Basic ${credentials}`
}
});Use apenas sobre HTTPS. Basic Auth codifica, mas não criptografa credenciais.
Redirecionamentos
const response = await fetch(url, {
redirect: 'manual'
});Seguir redirects de URLs externas pode levar a hosts internos. Para ferramentas de download, limite quantidade e valide cada destino.
Proteção contra SSRF
Antes de buscar uma URL fornecida pelo usuário:
- permita apenas protocolos HTTP e HTTPS;
- use allowlist de hosts quando possível;
- bloqueie loopback e redes privadas;
- revalide após DNS;
- controle redirects;
- limite tamanho e duração;
- não envie credenciais para outro host.
DNS rebinding e IPv6 exigem validação cuidadosa.
CORS
Clientes Node.js não aplicam as mesmas restrições de CORS dos navegadores. Isso não significa que qualquer destino é seguro. Autorização continua sendo responsabilidade do servidor remoto e da aplicação.
Cookies
O fetch do Node.js não mantém um cookie jar de navegador automaticamente. Para sessões, use uma biblioteca de cookies ou envie headers explicitamente, com proteção adequada.
FormData
const form = new FormData();
form.set('name', 'Ana');
form.set('document', fileBlob, 'document.pdf');
const response = await fetch(url, {
method: 'POST',
body: form
});Não defina manualmente o boundary do Content-Type. A implementação gera o header correto.
Upload de stream
Corpos em streaming podem exigir a opção duplex em versões compatíveis:
const response = await fetch(url, {
method: 'POST',
body: requestBody,
duplex: 'half'
});Teste a versão e o servidor. Limite o tamanho enviado e trate backpressure.
Response clone
const copy = response.clone();Clonar permite consumir o corpo duas vezes, mas pode acumular dados se um consumidor for lento. Evite com respostas grandes.
Headers de resposta
const contentType = response.headers.get('content-type');
const length = response.headers.get('content-length');Content-Length pode estar ausente ou incorreto. Ainda aplique limite durante leitura.
JSON inválido
let data;
try {
data = await response.json();
} catch {
throw new Error('Resposta JSON inválida');
}Não inclua todo o corpo na mensagem de erro, pois pode conter dados sensíveis.
Retries
Repita apenas falhas transitórias e operações idempotentes. Veja Retry com Backoff no Node.js.
for (let attempt = 0; attempt < 3; attempt += 1) {
try {
return await fetchWithTimeout(url);
} catch (error) {
if (!isRetryable(error) || attempt === 2) {
throw error;
}
await delay(calculateBackoff(attempt));
}
}Idempotency key
APIs de pagamento podem aceitar uma chave de idempotência. Gere uma por operação lógica e reutilize-a nos retries.
Dispatcher e conexões
O fetch nativo usa conceitos do Undici. Para personalizar pools, proxy ou conexão, use um dispatcher compatível com a versão.
Não passe http.Agent esperando controle direto. Consulte HTTP Agent no Node.js para a API tradicional.
Proxy
Ambientes corporativos podem exigir ProxyAgent ou configuração global. Valide credenciais, não registre URLs com senhas e restrinja destinos.
Observabilidade
Registre:
- método;
- host sanitizado;
- status;
- duração;
- bytes recebidos;
- tentativa;
- timeout ou cancelamento;
- identificador de correlação.
Não registre query strings ou headers sem filtragem.
OpenTelemetry
Instrumentação pode criar spans para chamadas externas. Veja OpenTelemetry no Node.js. Evite colocar corpo completo em atributos.
Testes
Teste com servidor local controlado:
- 200 com JSON;
- 404 e 500;
- resposta lenta;
- timeout;
- stream grande;
- redirect;
- JSON inválido;
- conexão encerrada;
- retry;
- cancelamento pelo usuário.
Erros comuns
- Não verificar response.ok: erros HTTP são tratados como sucesso.
- Usar text() em arquivo grande: memória cresce.
- Sem timeout: chamadas ficam penduradas.
- Retry de POST sem idempotência: efeitos duplicam.
- Confiar em URL externa: SSRF acessa rede interna.
- Registrar Authorization: tokens vazam.
- Esperar cookies automáticos: sessão não é preservada.
Boas práticas
- Verifique status.
- Defina timeouts.
- Use AbortSignal.
- Processe corpos grandes em streaming.
- Limite tamanho.
- Valide URLs e redirects.
- Proteja tokens.
- Use retries com backoff.
- Instrumente duração e status.
- Fixe a versão mínima do Node.js.
Conclusão
O Fetch Nativo no Node.js oferece uma interface moderna e portável para consumir APIs, enviar dados e trabalhar com Web Streams.
A simplicidade da chamada não elimina riscos operacionais. Status HTTP, timeouts, limites, SSRF, retries e credenciais precisam de tratamento explícito. Com essas proteções, fetch se torna uma base consistente para integrações sem depender de uma biblioteca adicional para cada requisição.




