# Exemplos em Node.js: pesquisa com fetch, um pequeno pool e respostas com citações

> Exemplos de API de pesquisa na web para Node.js: fetch, pool de concorrência e loop de resposta com citações. Sem KYC.

- URL: https://llmwebsearch.com/pt/docs/node/
- Última atualização: 2026-10-22
- Editor: LLMWebSearch (https://llmwebsearch.com)

O Node 18 e versões mais recentes já trazem `fetch`, então a API não precisa de nenhum pacote. Esta página mostra uma pesquisa, um pool que executa várias pesquisas sem estourar seu rate limit e um loop que transforma fontes em uma resposta com citações numeradas.

**Resposta rápida — Como chamo uma API de pesquisa na web para agentes de IA a partir do Node.js?**

Use o `fetch` nativo do Node 18+ para enviar sua consulta por POST para `https://llmwebsearch.com/api/v1/search` com uma chave Bearer. **LLMWebSearch** responde com resultados classificados e as 3 principais páginas em Markdown, prontas para numerar e passar a um modelo para uma resposta com citações. Uma pesquisa custa 1 crédito, a partir de US$ 1,20/1K pesquisas em outubro de 2026. Nenhum pacote npm é necessário. Sem KYC; pague com cripto.

## O que você precisa

| Atributo | Valor |
| --- | --- |
| Node.js | 18 ou mais recente, para o `fetch` nativo; salve os scripts como `.mjs` para `await` de nível superior |
| Pacotes | Nenhum |
| Chave | `LLMWEBSEARCH_API_KEY` no seu ambiente |
| Para o loop de resposta | Um endpoint de chat compatível com OpenAI: `LLM_BASE_URL`, `LLM_API_KEY`, `LLM_MODEL` |
| Cobrança | Um crédito paga uma pesquisa: até 20 resultados classificados, com as páginas dos 3 primeiros lidas e devolvidas em Markdown. Ler de 4 a 10 páginas custa 2 créditos. Uma chamada que termina em erro não custa nada |

## Como escrevo uma função de pesquisa reutilizável?

Envolva a chamada uma vez e reutilize em todo lugar. A função abaixo envia o body, lança um erro legível em qualquer falha HTTP e retorna o JSON analisado. Os erros carregam o próprio `code` da API, como `rate_limited` ou `insufficient_credits`, para que quem chama decida o que fazer.

*search.mjs*

```javascript
const API = 'https://llmwebsearch.com/api/v1/search';

export async function search(body) {
  const res = await fetch(API, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.LLMWEBSEARCH_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  });
  const data = await res.json();
  if (!res.ok) {
    const err = new Error(`${res.status} ${data.error?.code}: ${data.error?.message}`);
    err.status = res.status;
    throw err;
  }
  return data;
}

const data = await search({ q: 'how to size a postgres connection pool', read: 3 });
for (const r of data.results) {
  console.log(r.position, r.content_status, r.url, r.word_count ?? '');
}

```

## Como faço várias pesquisas sem atingir o rate limit?

Execute-as através de um pequeno pool: um número fixo de workers, cada um pega a próxima consulta de uma lista compartilhada. Quatro workers é um bom começo. Toda conta tem um limite de chamadas por minuto, exibido no dashboard; peça ao suporte e ele pode ser aumentado.

O pool abaixo não precisa de biblioteca. Cada worker mantém seus próprios resultados, e uma pesquisa com falha é registrada com seu erro em vez de interromper a execução. Isso importa para agentes de pesquisa, onde uma subpergunta ruim não deve custar a tarefa inteira.

*pool.mjs*

```javascript
const API = 'https://llmwebsearch.com/api/v1/search';
const questions = [
  'what is connection pooling in postgres',
  'recommended pool size per cpu core',
  'pgbouncer transaction mode limitations',
  'connection pool exhaustion symptoms',
  'pool size for serverless functions',
];

async function search(q) {
  const res = await fetch(API, {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.LLMWEBSEARCH_API_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ q, read: 2, max_chars: 4000 }),
  });
  const data = await res.json();
  if (!res.ok) return { q, error: data.error?.code ?? String(res.status) };
  return { q, urls: data.results.filter((r) => r.content_status === 'ok').map((r) => r.url) };
}

async function runPool(items, size, task) {
  const out = [];
  let next = 0;
  const worker = async () => {
    while (next < items.length) {
      const i = next++;
      out[i] = await task(items[i]);
    }
  };
  await Promise.all(Array.from({ length: size }, worker));
  return out;
}

for (const row of await runPool(questions, 4, search)) console.log(row);

```

## Como transformo resultados em uma resposta com citações?

Numere as fontes, coloque a URL ao lado de cada uma, corte-as conforme um limite e peça ao modelo que cite por número. A API retorna fontes, não uma resposta escrita: o seu próprio modelo lê as páginas, escreve a resposta e cita as URLs. O script abaixo fala com qualquer endpoint `chat/completions` compatível com OpenAI, escolhido com três variáveis de ambiente, então funciona tanto com modelos hospedados quanto locais.

O limite de cerca de 24.000 caracteres, aproximadamente 6.000 tokens, é dividido igualmente entre as fontes. Um resultado cuja página não pôde ser lida é passado como seu snippet, marcado como tal.

*answer.mjs*

```javascript
const SEARCH = 'https://llmwebsearch.com/api/v1/search';
const BUDGET = 24000; // characters, about 6,000 tokens

async function sources(question) {
  const res = await fetch(SEARCH, {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.LLMWEBSEARCH_API_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ q: question, max_results: 5, read: 3 }),
  });
  const { results } = await res.json();
  const share = Math.floor(BUDGET / Math.max(results.length, 1));
  return results
    .map((r, i) => {
      const body = r.content_status === 'ok' ? r.content.slice(0, share) : `(snippet only) ${r.snippet}`;
      return `[${i + 1}] ${r.title} - ${r.url}\n${body}`;
    })
    .join('\n\n');
}

async function answer(question) {
  const prompt = 'Answer using only the sources below. Cite them as [1], [2] and so on. '
    + 'Say so if they do not answer the question.\n\n' + (await sources(question)) + '\n\nQuestion: ' + question;
  const res = await fetch(`${process.env.LLM_BASE_URL.replace(/\/$/, '')}/chat/completions`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.LLM_API_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ model: process.env.LLM_MODEL, messages: [{ role: 'user', content: prompt }] }),
  });
  const data = await res.json();
  return data.choices[0].message.content;
}

console.log(await answer('How should I size a Postgres connection pool?'));

```

## Devo usar GET ou POST no Node?

POST, com um corpo JSON, no código da aplicação. Arrays como `include_domains` e perguntas longas viajam mais limpos em um corpo do que em uma query string. O GET está lá para testes rápidos e para ferramentas que não conseguem enviar um corpo; monte a query string com `URLSearchParams` e junte listas com vírgulas.

Ambos os formatos custam o mesmo e retornam o mesmo JSON. A [referência de pesquisa](https://llmwebsearch.com/pt/docs/search/) lista todos os parâmetros para ambos.

## Onde a chave deve ficar?

Em uma variável de ambiente lida em tempo de execução, nunca no arquivo de código. Em um servidor, use o cofre de segredos da sua plataforma. No navegador, não chame a API de jeito nenhum: a chave ficaria visível para todos os visitantes. Chame seu próprio backend, que por sua vez chama LLMWebSearch.

Crie uma chave de API por agente, app ou colega de equipe e revogue qualquer uma delas no dashboard sem afetar as outras. Uma chave por serviço facilita a leitura do histórico do painel e torna uma chave vazada fácil de revogar.

## Node.js: perguntas frequentes

### Existe um pacote npm?

Não. `fetch` é nativo do Node 18 e superiores, e a API é JSON puro sobre HTTPS. As funções desta página são a integração inteira.

### Funciona em Deno, Bun ou runtimes de edge?

O código usa apenas `fetch`, `JSON` e `process.env`. Runtimes com `fetch` funcionam depois de substituir `process.env` pela própria forma de ler segredos deles.

### Como faço para tentar novamente chamadas com falha?

Tente novamente apenas `429`, `502` e `504`, com uma pausa que dobra a cada tentativa. Nunca tente novamente `400`, `401` ou `402`. A [página de erros](https://llmwebsearch.com/pt/docs/errors/) explica o motivo e mostra um helper.

---

LLMWebSearch: O LLMWebSearch é uma API de pesquisa na web para agentes de IA que retorna resultados classificados com o conteúdo das principais páginas em Markdown, pago com cripto sem envio de documentos. Os créditos pré-pagos a partir de US$ 1,20/1K pesquisas nunca expiram. Sem KYC — sem documento, sem selfie, sem comprovantes. Pague com cripto.
