A primeira vez que subi uma API integrada em produção, esqueci de tratar timeout na chamada externa. Deu tudo certo em teste local, com a API respondendo em 200ms. Em produção, num horário de pico, a API terceira demorou 30 segundos pra responder um dia, e meu servidor Node ficou com dezenas de requisições penduradas esperando, até travar. Validar a resposta da API não é suficiente — também importa nunca confiar que ela vai responder rápido, ou responder de jeito nenhum.
Esse guia mostra como consumir uma API externa a partir de um backend em Node.js, com os cuidados que só aparecem depois que algo quebra em produção.
Configurando o projeto
Com o Node.js instalado (baixe a versão LTS no site oficial, evite a “Current” em produção), inicialize o projeto e instale as dependências básicas:
mkdir projeto-api && cd projeto-api
npm init -y
npm install express axios dotenv
O dotenv não é opcional aqui — é o que evita a chave de API parar hardcoded no código e acabar num commit público do GitHub. Crie um arquivo .env na raiz e adicione .env ao .gitignore antes de qualquer outra coisa, não depois.
Servidor básico
Um server.js simples pra servir de base:
require('dotenv').config();
const express = require('express');
const axios = require('axios');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json());
app.get('/', (req, res) => {
res.send('Servidor rodando');
});
app.listen(PORT, () => {
console.log(`Rodando em http://localhost:${PORT}`);
});
Consumindo a API externa (com os cuidados que evitam dor de cabeça)
Aqui está a diferença entre um endpoint que funciona em teste e um que aguenta produção: timeout explícito, tratamento de erro específico por tipo de falha, e nunca expor o erro bruto da API terceira pro cliente final.
app.get('/clima', async (req, res) => {
const cidade = req.query.cidade || 'Sao Paulo';
const url = `https://api.openweathermap.org/data/2.5/weather`;
try {
const resposta = await axios.get(url, {
params: { q: cidade, appid: process.env.API_KEY, units: 'metric' },
timeout: 5000
});
res.json(resposta.data);
} catch (erro) {
if (erro.code === 'ECONNABORTED') {
return res.status(504).json({ erro: 'A API externa demorou demais pra responder' });
}
if (erro.response) {
return res.status(erro.response.status).json({ erro: 'API externa recusou a requisição' });
}
res.status(500).json({ erro: 'Falha ao buscar dados' });
}
});
O timeout: 5000 é o que teria evitado meu problema em produção: sem ele, o Axios espera indefinidamente, e cada requisição pendurada consome uma conexão do pool do Node até esgotar. Separar o tratamento por erro.code (falha de rede/timeout) de erro.response (a API respondeu, mas com erro) também importa — são causas diferentes e merecem mensagens diferentes pra quem for debugar às três da manhã.
CORS: o erro que todo iniciante bate a cabeça
Se o front-end (rodando em outra porta ou domínio) tentar chamar a API diretamente sem passar pelo seu backend Node, o navegador bloqueia por CORS — e a mensagem de erro no console geralmente não deixa óbvio que o problema é esse. A solução mais simples é justamente centralizar a chamada à API externa no seu backend (como no exemplo acima) e deixar o front-end falar só com o seu próprio servidor, que não tem essa restrição entre back-ends. Se mesmo assim precisar liberar CORS pro seu próprio front-end acessar o backend Node, o pacote cors resolve com poucas linhas — mas configurar origin: '*' em produção é abrir mão de uma camada de proteção real, vale restringir ao domínio específico do seu site.
Rate limiting: a API externa também tem limites
Toda API de terceiro tem um limite de requisições por minuto ou por dia, mesmo no plano pago — e estourar esse limite geralmente não trava graciosamente, retorna um erro 429 que precisa ser tratado como qualquer outro. Se o endpoint recebe muito tráfego, vale implementar um cache simples (mesmo que só em memória, com um Map e um timestamp de expiração) pra não bater na API externa a cada requisição repetida — isso economiza cota da API e deixa a resposta mais rápida pro usuário final.
Consumindo a partir do front-end
Do lado do HTML, um fetch simples já resolve, sempre tratando o caso de erro (a maioria dos tutoriais mostra só o caminho feliz):
fetch('/clima?cidade=Rio de Janeiro')
.then(r => r.ok ? r.json() : Promise.reject(r.status))
.then(dados => {
document.getElementById('clima').innerHTML =
`${dados.name}: ${dados.main.temp}°C`;
})
.catch(erro => console.error('Falha ao carregar clima:', erro));
Perguntas frequentes
Preciso de um framework como Express, ou dá pra usar Node puro?
Dá pra usar o módulo http nativo, mas o Express economiza tanto código repetitivo de roteamento e parsing que raramente vale a pena abrir mão dele, mesmo em projetos pequenos.
Onde guardar a API_KEY em produção, já que o .env não vai pro Git?
A maioria dos serviços de hospedagem (Railway, Render, Vercel etc.) tem um painel de variáveis de ambiente separado — a chave é cadastrada lá, não no arquivo .env, que fica só para desenvolvimento local.
Axios é necessário ou o fetch nativo do Node já resolve?
Node 18+ já tem fetch nativo, então tecnicamente não é obrigatório. Mas o Axios ainda facilita bastante coisas como timeout, interceptors e parsing automático de JSON com menos código.
Como lidar com uma API que exige autenticação OAuth em veh de chave simples?
O fluxo muda bastante — geralmente envolve trocar credenciais por um token de acesso que expira e precisa ser renovado. Vale tratar isso como uma camada separada (um middleware que garante um token válido antes de cada chamada), em vez de misturar a lógica de autenticação dentro de cada endpoint.
Leia também: Como Configurar um Ambiente Docker do Zero, O Que É Jamstack e Por Que Usar em Seus Projetos e Como Publicar Site no GitHub Pages Grátis.


