Por que priorizar o fetch nativo? E como implementar timeout nas requisições
Introdução
Node.js
Na Harmo, nosso codebase é bastante diverso. Sempre buscamos equilíbrio entre as linguagens, usando cada uma para aquilo que faz de melhor. Começamos nosso core em Node.js e, desde 2017, evoluímos muito com Golang (desde a versão 1.7). Hoje, também contamos com Python, Vue.js e até shell scripts para infra.
No ecossistema Node.js é comum haver múltiplas formas de resolver o mesmo problema. Por isso, adotamos uma diretriz clara: sempre priorizar o que é nativo, evitando dependências externas desnecessárias. Quando precisamos de algo mais específico, criamos pacotes privados que abstraem essa lógica, garantindo reaproveitamento e segurança.
Dessa forma, para gerir os clients back-end que fazem requisições HTTPS (internas e externas), usamos desde o Node v18 o fetch nativo. Porém, ele não possui uma forma “oficial” de configurar timeout. Vamos ver como contornar isso.

Distribuição das linguagens no codebase da Harmo
Por que usar fetch nativo?
A primeira pergunta que sempre aparece é: “Por que usar algo nativo e contornar essa limitação, se eu posso importar o Axios e setar timeout facilmente?”.
Pontos que pesam a favor do fetch:
- Livre de dependências externas: menos pacotes para atualizar, menos riscos de vulnerabilidades.
- Adoção e padronização: hoje é API estável, padronizada e suportada oficialmente no Node 18+.
- API moderna: suporta streaming, trabalha com Promises e se alinha ao padrão da Web.
Comparativo mais amplo — fetch vs Axios
Zero dependências, menos superfície de risco
- Nada de instalar libs só para HTTP.
- Menos código de terceiros no deploy, menos CVEs para monitorar.
Padrão da Web (WHATWG) e futuro-proof
- Mesma API usada em navegadores, Deno, Bun, runtimes serverless/edge.
- Isomórfico: o mesmo código funciona em front e back.
Disponível nativamente no Node 18+
- Já faz parte do runtime, baseado em undici.
- Suporte a
AbortControllereAbortSignal.timeout(ms).
API moderna e composável
Request,Response,Headerspadronizados.- Composição simples via funções, sem precisar de interceptors acoplados.
Performance e simplicidade operacional
- Menos camadas ⇒ overhead menor.
- Tree-shaking natural: usa a API do runtime, não importa um bundle extra.
E quando faz sentido usar Axios (ou outra lib)?
Não é sobre “nunca usar Axios”, mas sim começar com fetch e só adicionar uma lib se o caso justificar.
Exemplos em que pode valer a pena:
- Interceptors prontos para logging, métricas ou autenticação.
- Retries/backoff já implementados.
- Serialização automática de
paramse normalização de erros. - Ambientes legados (Node < 18).
- Ergonomia de configuração (
axios.create, defaults globais, etc.).
Problema de Timeout e helpers
O fetch não tem uma opção timeout nativa no objeto de configuração.
Se você não tratar isso, uma requisição pode ficar pendurada indefinidamente, travando fluxo e degradando UX.
Solução 1: AbortController + setTimeout
async function fetchWithTimeout(url, options = {}, timeout = 5000) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeout);
try {
return await fetch(url, { ...options, signal: controller.signal });
} finally {
clearTimeout(timer);
}
}
Solução 2: AbortSignal.timeout (mais moderna)
const response = await fetch("https://api.exemplo.com", {
signal: AbortSignal.timeout(3000),
});
Conclusão
Como trabalhamos com microsserviços e serverless, estamos sempre atualizando nosso codebase e priorizando recursos nativos.
- O fetch nativo deve ser sempre a primeira escolha: leve, padronizado e futuro-proof;
- Timeout não é um problema: com AbortController ou AbortSignal.timeout, você cobre 99% dos cenários;
- Axios e outras libs ainda têm espaço, mas só quando há uma necessidade clara (interceptors complexos, retrys prontos, ergonomia global).
Lições aprendidas
Interessante refletir e analisar cada caso, mas o que sempre buscamos ter aqui é o básico bem feito.
- Comece simples: fetch nativo atende a maioria dos casos;
- Priorize o controle: implemente timeout sempre, mesmo em chamadas internas;
- Reduza dependências: cada pacote a menos significa menos riscos, menos manutenção e menos bugs;
- Crie seus utilitários: pequenos helpers (fetchJson, fetchWithRetry) podem substituir dezenas de linhas de configs em libs externas;
- Padronize no time: uma decisão simples como “usar fetch nativo com helper padrão” já elimina inconsistências e acelera onboarding de devs.
💬 E você, como lida com requisições no Node.js?
Prefere ficar só no fetch nativo ou ainda vê valor em libs como Axios? Deixa sua experiência nos comentários, quero muito ouvir como outros times têm tratado esse tema no dia a dia.