Geonode logo
Geonode Team

Geonode Team

Atualizado: 7 de outubro de 2026

Publicado: 2 de setembro de 2026

Definir cabeçalhos com o node-fetch

O Node dispõe de um ``fetch`` integrado desde a versão 18, o que significa que a maioria das pessoas que procura por ``node-fetch`` já não precisa, de todo, desse pacote. A configuração de cabeçalhos é simples em ambos os casos. O que é menos óbvio é quando `set` difere de `append`, quais os cabeçalhos que não é permitido definir e por que razão um cabeçalho que configurou não aparece na transmissão. Este guia aborda tudo isto, além do comportamento do proxy que difere de todos os outros clientes HTTP no Node.

O nosso problema reside numa armadilha específica: somos a Geonode e vendemos proxies, e o fetch integrado no Node ignora completamente as variáveis de ambiente HTTP_PROXY e HTTPS_PROXY. Todos os outros clientes HTTP do ecossistema respeitam-nas, pelo que as pessoas configuram um proxy, vêem que os pedidos são bem-sucedidos e assumem que está a funcionar — enquanto o tráfego segue diretamente. Não há qualquer aviso nem erro. A correção consiste em algumas linhas e encontra-se na secção sobre proxies abaixo. Se estiver a utilizar um proxy para o tráfego do Node fetch e não tiver definido explicitamente um dispatcher, é quase certo que as suas solicitações não estão a passar pelo seu proxy.

Utilizar o fetch nativo ou o pacote node-fetch?

Comece por aqui, pois isto determina o que irá instalar.

A documentação do Node's indica que o fetch foi adicionado nas versões v17.5.0 e v16.15.0, deixou de estar sujeito ao sinalizador --experimental-fetch a partir da versão v18.0.0 e «já não é experimental» a partir da versão v21.0.0. É descrito como «uma implementação compatível com navegadores da função fetch(), baseada no undici, um cliente HTTP/1.1 escrito de raiz para o Node.js». Headers, Request e Response seguem a mesma cronologia.

Assim, em qualquer versão do Node atualmente suportada, fetch é uma variável global e não é necessária qualquer dependência.

O pacote node-fetch continua a ser útil em dois casos: na manutenção de código num ambiente de execução mais antigo e quando é necessário recorrer a um dos poucos comportamentos em que a sua API difere. Note-se que a versão 3 é exclusivamente ESM, o que cria dificuldades a projetos que ainda utilizam require.

Tudo o que se segue aplica-se a ambos, uma vez que a API é deliberadamente a mesma.

As três formas de definir cabeçalhos

Um objeto simples — o caso mais comum e aquele a utilizar na maioria das vezes:

const res = await fetch("https://api.example.com/items", {
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer eyJhbG...",
    "Accept": "application/json",
  },
});

**Um objeto ``Headers`

`** — quando se constrói a configuração de forma condicional:

const headers = new Headers({ "Accept": "application/json" });
if (token) headers.set("Authorization", `Bearer ${token}`);
if (locale) headers.set("Accept-Language", locale);

const res = await fetch(url, { headers });

Um array de pares — útil quando um cabeçalho se repete legitimamente:

const res = await fetch(url, {
  headers: [
    ["Accept", "application/json"],
    ["X-Trace", "a"],
    ["X-Trace", "b"],
  ],
});

Todas as três formas são equivalentes no caso simples. O objeto ``Headers`

` justifica a sua utilização quando é necessária lógica condicional ou quando se pretende inspecionar o que foi construído antes do envio.

set

versus append : a distinção que causa surpresas.

Os documentos da MDN definem set() como a definição de «um novo valor para um cabeçalho existente» e a substituição dos valores existentes, enquanto append() «acrescenta um novo valor a um cabeçalho existente ou adiciona-o caso não exista».

const h = new Headers();
h.append("X-Custom", "one");
h.append("X-Custom", "two");
h.get("X-Custom");        // "one, two"

h.set("X-Custom", "three");
h.get("X-Custom");        // "three"

append acumula; set substitui. Para quase todos os cabeçalhos que enviar, set é o que pretende — enviar dois valores Authorization não é um pedido significativo. append é relevante para cabeçalhos em que vários valores são legítimos e, na prática, essa lista é curta.

Os nomes dos cabeçalhos não distinguem maiúsculas de minúsculas. A MDN refere que são «comparados com base numa sequência de bytes que não distingue maiúsculas de minúsculas» em todos os métodos, pelo que h.get("content-type") e h.get("Content-Type") devolvem o mesmo valor. Escolha uma convenção para facilitar a leitura e deixe de se preocupar com isso.

Existe também has() para testar a presença, delete() para remover e getSetCookie() , que devolve um array com todos os valores de Set-Cookie — necessário porque esse cabeçalho é o principal caso em que vários valores coexistem genuinamente e um simples get() os concatenaria num resultado que não seria possível dividir de forma fiável.

Cabeçalhos que não é possível definir

A razão pela qual um cabeçalho que configurou não aparece.

A MDN descreve uma «proteção» nos objetos Headers que determina o que pode ser modificado. Um new Headers() independente não tem restrições. Os cabeçalhos associados a um Request permitem a modificação de «cabeçalhos de pedido não proibidos». E os cabeçalhos num Response obtidos «a partir de Response.error(), Response.redirect() ou fetch()» são imutáveis — não é possível alterar os cabeçalhos de uma resposta após a sua receção.

Os cabeçalhos de pedido proibidos são aqueles controlados pelo ambiente de execução, e as tentativas de os definir são ignoradas silenciosamente, em vez de gerarem um erro. A lista inclui Host, Connection, Content-Length, Transfer-Encoding, Origin, Referer em alguns contextos, e as famílias com os prefixos Sec- e Proxy-.

Duas consequências práticas.

O silêncio é o modo de falha. Sem exceção, sem aviso; o cabeçalho simplesmente não é enviado. Se um servidor insistir que não está a receber algo que definiu, verifique o que realmente foi transmitido em vez de reler o seu código.

O Node é mais permissivo do que um navegador em alguns destes casos, porque não há nenhuma origem a proteger. Código que define um cabeçalho com sucesso no Node pode ver esse cabeçalho ser ignorado num navegador, o que constitui uma verdadeira armadilha de portabilidade para código partilhado.

Para verificar o que enviou efetivamente, envie um pedido a um serviço que o devolva:

const res = await fetch("https://httpbin.org/headers", {
  headers: { "X-Test": "value", "User-Agent": "MyBot/1.0" },
});
console.log(await res.json());

Leitura de cabeçalhos de resposta

A outra parte, e há um comportamento que vale a pena conhecer.

const res = await fetch(url);

res.headers.get("content-type");
res.headers.has("etag");

for (const [name, value] of res.headers) {
  console.log(name, value);
}

A iteração produz nomes em minúsculas, uma vez que o conjunto de cabeçalhos é normalizado.

**Set-Cookie

requer um tratamento especial.** Vários cookies chegam como vários cabeçalhos, e um simples get("set-cookie")

apresenta-os separados por vírgulas — o que é ambíguo, porque os próprios valores dos cookies podem conter vírgulas numa data Expires

. getSetCookie()

existe precisamente para isso e devolve um array:

const cookies = res.headers.getSetCookie();

Os cabeçalhos de resposta são imutáveis. Não é possível modificar o que fetch

devolveu. Se precisar de uma versão alterada, construa um novo Response

.

E a verificação que importa mais do que qualquer cabeçalho: fetch

não rejeita em caso de códigos de erro HTTP. Um 404 ou um 500 são resolvidos normalmente, pelo que res.ok

deve ser testado antes de interpretar o corpo.

const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status} from ${url}`);

Ignorar isso é a causa direta de Unexpected token '<'

numa chamada a .json()

— analisou uma página de erro.

Cabeçalhos predefinidos e o que o Node adiciona

O Node define vários cabeçalhos automaticamente, e saber quais são evita confusões.

Host — derivado do URL, não configurável. Connection — gerido pelo conjunto de ligações. Content-Length — calculado a partir do corpo da resposta. Accept — o valor predefinido é */*, a menos que o defina. Accept-Encoding — o Node indica que suporta compressão e descomprime a resposta de forma transparente. User-Agent — o Node envia o seu próprio por predefinição, identificando normalmente o undici.

Este último aspeto é importante para qualquer interação com terceiros. Um agente de utilizador de tempo de execução predefinido é uma identificação precisa e, para clientes automatizados, uma identificação fraca — um nome honesto com um URL de contacto é tratado melhor do que uma cadeia de caracteres anónima de tempo de execução:

headers: { "User-Agent": "AcmeBot/1.0 (+https://acme.example.com/bot)" }

Uma nota sobre os corpos: quando passar um objeto FormData, não defina Content-Type manualmente. O ambiente de execução deve gerá-lo, porque contém o limite multipart, e substituí-lo produz uma solicitação que o servidor não consegue analisar. Esta é uma das causas mais comuns de um erro 400 ou 415 inexplicável.

Definir cabeçalhos para cada pedido

Para tudo o que não seja um script, centralize-o.

const DEFAULTS = {
  "Accept": "application/json",
  "User-Agent": "AcmeBot/1.0 (+https://acme.example.com/bot)",
};

async function api(path, options = {}) {
  const res = await fetch(`https://api.example.com${path}`, {
    ...options,
    headers: { ...DEFAULTS, ...options.headers },
  });
  if (!res.ok) {
    const body = await res.text();
    throw new Error(`HTTP ${res.status} ${path}: ${body.slice(0, 200)}`);
  }
  return res;
}

Há dois pormenores aqui que merecem destaque. Definir os valores por defeito primeiro significa que quem faz a chamada pode substituir qualquer um deles, o que é precisamente o comportamento pretendido. E incluir os primeiros 200 caracteres do corpo do erro transforma um código de estado opaco numa mensagem com base na qual se pode agir.

Note que a propagação do objeto é superficial e corresponde à sequência exata da chave; por isso, "content-type" nas opções do chamador não substituirá "Content-Type" nos valores predefinidos — enviará ambos, e o tempo de execução escolherá um. Se os chamadores puderem usar maiúsculas e minúsculas arbitrárias, crie um objeto Headers e deixe que o seu set(), insensível a maiúsculas e minúsculas, trate da fusão corretamente.

Depuração de cabeçalhos que não estão a funcionar

Uma sequência que resolve praticamente todos os problemas de cabeçalhos em poucos minutos, na ordem que elimina o maior número de possibilidades.

Primeiro — verifique o que foi realmente enviado pela rede. Nada mais nesta lista importa até que tenha feito isto. Um serviço de eco de cabeçalhos é a forma mais rápida:

const res = await fetch("https://httpbin.org/headers", { headers: myHeaders });
console.log(JSON.stringify(await res.json(), null, 2));

Se o seu cabeçalho não estiver aqui, significa que nunca saiu do seu processo — está proibido, está mal escrito ou foi sobrescrito. Se estiver aqui e o destino indicar o contrário, algo entre si e o destino está a removê-lo.

Segundo — crie o objeto Headers e inspecione-o antes de enviar. Isto distingue «construí-o incorretamente» de «o ambiente de execução descartou-o»:

const h = new Headers(myHeaders);
console.log([...h.entries()]);

A construção de um objeto Headers aplica a mesma normalização que o ambiente de execução aplicaria, pelo que um nome que sobreviva aqui é um nome que será enviado.

Três — verifique se há duplicações acidentais. A armadilha da fusão superficial: uma expansão de objeto compara chaves por cadeia de caracteres exata, pelo que {...{"Content-Type": "a"}, ...{"content-type": "b"}} produz ambas as entradas. Crie um objeto Headers e utilize set() se os chamadores puderem fornecer maiúsculas e minúsculas arbitrárias, uma vez que a sua correspondência insensível a maiúsculas e minúsculas realiza a fusão corretamente.

Quatro — reproduza-o no curl. Se a mesma solicitação funcionar a partir de um terminal e não a partir do Node, a diferença está no seu código e não no servidor:

curl -v -H "Authorization: Bearer $TOKEN" https://api.example.com/items 2>&1 | grep '^>'

Comparar os dois blocos > lado a lado geralmente torna a diferença óbvia.

Cinco — leia a resposta na íntegra, não apenas o código de estado. Um código de estado 400 ou 401 inclui frequentemente um corpo que explica exatamente qual o cabeçalho que estava errado, e o código que o descarta está a desperdiçar a resposta:

if (!res.ok) console.error(res.status, (await res.text()).slice(0, 300));

E verifique o redirecionamento. O fetch segue redirecionamentos por predefinição, e alguns cabeçalhos — em particular o Authorization — são descartados quando um redirecionamento passa para uma origem diferente. Se uma solicitação funcionar diretamente na URL final, mas não na original, essa é quase certamente a razão.

A armadilha do proxy

O comportamento que difere de todos os outros clientes HTTP do Node e a razão pela qual esta secção existe.

**O ``fetch`

do Node não lê ``HTTP_PROXY

, ``HTTPS_PROXY

ou ``NO_PROXY

`.** Definir esses valores não altera nada. Os pedidos são enviados diretamente, são bem-sucedidos e nada indica que o proxy foi contornado.

A solução é o ``ProxyAgent`

` do undici:

import { ProxyAgent, setGlobalDispatcher } from "undici";

setGlobalDispatcher(new ProxyAgent("http://user:pass@proxy.example.com:9000"));

// now every fetch in this process goes through the proxy
const res = await fetch("https://api.example.com/items");

Para uma única solicitação, em vez de todo o processo, passe um dispatcher por chamada:

const agent = new ProxyAgent("http://proxy.example.com:9000");
const res = await fetch(url, { dispatcher: agent });

Note que ``dispatcher`

` é uma extensão específica do Node, e não faz parte da API Fetch padrão; por isso, o código que a utiliza não é portável para um navegador.

Verifique sempre se a alteração surtiu efeito. Pergunte a um serviço qual o endereço que este vê, com e sem o despachante:

const res = await fetch("https://api.ipify.org?format=json");
console.log(await res.json());

Se o endereço não mudar, o proxy não está no caminho — e, dado que não há nenhum erro a alertá-lo, esta verificação é a única coisa que separa uma configuração funcional de uma que é silenciosamente contornada. Trata-se do mesmo tipo de falha silenciosa sobre a qual escrevemos em por que é importante testar proxies.

As diferenças de comportamento entre os clientes HTTP neste contexto são exatamente o tipo de coisa que comparámos em axios vs fetch.

Perguntas frequentes

Como configuro os cabeçalhos com o node-fetch?

Passe um objeto headers nas opções: fetch(url, { headers: { "Authorization": "Bearer ..." } }). Também pode passar uma instância de Headers ou um array de pares nome-valor. A mesma sintaxe funciona com o método fetch integrado do Node.

Ainda preciso do pacote node-fetch?

Normalmente, não. O Node tem uma variável global fetch desde a versão 17.5.0, sem marcação desde a versão 18 e estável desde a versão 21. Instale o pacote apenas para ambientes de execução mais antigos ou para uma diferença de comportamento específica — e tenha em atenção que a versão 3 é apenas para ESM.

Qual é a diferença entre headers.set e headers.append?

set substitui qualquer valor existente para esse cabeçalho; append adiciona outro valor, pelo que duas chamadas a append produzem uma lista separada por vírgulas. Utilize set para quase tudo — append só é relevante para cabeçalhos em que vários valores são válidos.

Porque é que o meu cabeçalho não está a ser enviado?

Provavelmente trata-se de um cabeçalho proibido que o ambiente de execução controla — Host, Connection, Content-Length e a família Sec-, entre outros. Estes são ignorados silenciosamente, em vez de gerarem um erro. Envie um pedido a um serviço de eco de cabeçalhos para ver o que realmente foi transmitido.

Os nomes dos cabeçalhos distinguem maiúsculas de minúsculas no fetch?

Não. O MDN especifica que os nomes dos cabeçalhos são comparados com base numa sequência de bytes que não distingue maiúsculas de minúsculas em todos os métodos Headers, pelo que get("content-type") e get("Content-Type") são equivalentes. A iteração de um objeto Headers produz nomes em minúsculas.

Como posso ler vários cabeçalhos Set-Cookie?

Utilize res.headers.getSetCookie(), que devolve um array. Um simples get("set-cookie") junta-os com vírgulas, o que é ambíguo porque os próprios valores dos cookies podem conter vírgulas numa data Expires.

Por que é que o Node fetch ignora a minha configuração HTTP_PROXY?

Porque não lê essas variáveis de ambiente de todo, ao contrário de quase todos os outros clientes HTTP do Node. Utilize o ProxyAgent do undici com setGlobalDispatcher, ou passe um dispatcher por pedido — e, em seguida, verifique o endereço de saída, uma vez que um proxy contornado não produz qualquer erro.

Devo definir o Content-Type ao enviar FormData?

Não. O tempo de execução gera-o incluindo o delimitador multipart, e defini-lo manualmente remove esse delimitador, produzindo uma solicitação que o servidor não consegue analisar. Esta é uma causa comum de respostas 400 e 415 inexplicáveis.

Conclusão

Definir cabeçalhos no Node é uma questão de uma única linha, independentemente da API que utilizar, e o fetch integrado significa que a maioria dos projetos já nem sequer precisa de um pacote para o efeito.

Três comportamentos são responsáveis por quase toda a confusão. set substitui, enquanto append acumula, e inverter essa ordem produz valores de cabeçalho separados por vírgulas que os servidores rejeitam. Os cabeçalhos proibidos são ignorados silenciosamente, em vez de gerarem um erro; por isso, um cabeçalho que o servidor não esteja a receber tem de ser verificado na transmissão, em vez de ser relido no seu editor. E fetch resolve-se em erros HTTP, pelo que res.ok tem de ser verificado antes que o corpo tenha qualquer significado.

A armadilha específica do Node é a do proxy, e vale a pena repeti-la porque falha de forma tão silenciosa: o fetch integrado ignora completamente o HTTP_PROXY. Se precisar de tráfego por proxy, defina um despachante explicitamente — e, em seguida, confirme o endereço de saída, porque uma configuração que não faz nada parece exatamente igual a uma que funciona.