# Esempi Node.js: ricerca con fetch, un piccolo pool e risposte con citazioni

> API di ricerca web per Node.js: fetch, pool di concorrenza e ciclo di risposte con citazioni per modelli compatibili con OpenAI, senza KYC in criptovalute.

- URL: https://llmwebsearch.com/it/docs/node/
- Ultimo aggiornamento: 2026-10-22
- Editore: LLMWebSearch (https://llmwebsearch.com)

Node 18 e versioni successive includono `fetch`, quindi l'API non richiede alcun pacchetto. Questa pagina mostra una ricerca, un pool che esegue più ricerche senza saturare il tuo rate limit, e un ciclo che trasforma le fonti in una risposta con citazioni numerate.

**Risposta rapida — Come chiamo un'API di ricerca web per agenti AI da Node.js?**

Usa il `fetch` integrato di Node 18+ per inviare la tua query in POST a `https://llmwebsearch.com/api/v1/search` con una chiave Bearer. **LLMWebSearch** risponde con risultati classificati e le prime 3 pagine in Markdown, pronti da numerare e passare a un modello per una risposta con citazioni. Una ricerca costa 1 credito, a partire da 1,20 USD/1K ricerche a ottobre 2026. Nessun pacchetto npm necessario. Senza KYC; paghi in criptovalute.

## Cosa ti serve

| Attributo | Valore |
| --- | --- |
| Node.js | 18 o superiore, per il `fetch` integrato; salva gli script come `.mjs` per il `await` di primo livello |
| Pacchetti | Nessuno |
| Chiave | `LLMWEBSEARCH_API_KEY` nel tuo ambiente |
| Per il ciclo di risposta | Un endpoint di chat compatibile con OpenAI: `LLM_BASE_URL`, `LLM_API_KEY`, `LLM_MODEL` |
| Fatturazione | Un credito paga una ricerca: fino a 20 risultati classificati, con le pagine dei primi 3 lette e restituite come Markdown. Leggere da 4 a 10 pagine costa 2 crediti. Una chiamata che termina in errore non costa nulla |

## Come scrivo una funzione di ricerca riutilizzabile?

Avvolgi la chiamata una volta e riutilizzala ovunque. La funzione qui sotto invia il body, lancia un errore leggibile per qualsiasi errore HTTP e restituisce il JSON analizzato. Gli errori portano il `code` dell'API stessa, come `rate_limited` o `insufficient_credits`, così chi chiama può decidere cosa fare.

*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 ?? '');
}

```

## Come posso eseguire più ricerche senza incorrere nel rate limit?

Eseguile tramite un piccolo pool: un numero fisso di worker che prendono ciascuno la query successiva da una lista condivisa. Quattro worker sono un buon punto di partenza. Ogni account ha un limite di chiamate al minuto, mostrato nella dashboard; chiedi al supporto e può essere alzato.

Il pool qui sotto non richiede alcuna libreria. Ogni worker mantiene i propri risultati e una ricerca fallita viene registrata con il suo errore invece di interrompere l'esecuzione. Questo conta per gli agenti di ricerca, dove una sotto-domanda sbagliata non dovrebbe compromettere l'intero task.

*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);

```

## Come trasformo i risultati in una risposta con citazioni?

Numera le fonti, metti l'URL accanto a ciascuna, riducile a un budget e chiedi al modello di citare per numero. L'API restituisce fonti, non una risposta scritta: il tuo modello legge le pagine, scrive la risposta e cita gli URL. Lo script qui sotto comunica con qualsiasi endpoint `chat/completions` compatibile con OpenAI, scelto tramite tre variabili d'ambiente, quindi funziona sia con modelli hostati che locali.

Il budget di circa 24.000 caratteri, all'incirca 6.000 token, è diviso equamente tra le fonti. Un risultato la cui pagina non è stata letta viene passato come snippet, contrassegnato come tale.

*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 usare GET o POST da Node?

POST, con un body JSON, nel codice dell'applicazione. Gli array come `include_domains` e le domande lunghe viaggiano più puliti in un body che in una query string. GET serve per test rapidi e per strumenti che non possono inviare un body; costruisci la sua query string con `URLSearchParams` e unisci le liste con virgole.

Entrambe le forme costano lo stesso e restituiscono lo stesso JSON. Il [riferimento della ricerca](https://llmwebsearch.com/it/docs/search/) elenca ogni parametro per entrambe.

## Dove dovrebbe stare la chiave?

In una variabile d'ambiente letta a runtime, mai nel file sorgente. Su un server, usa il secret store della tua piattaforma. In un browser, non chiamare affatto l'API: la chiave sarebbe visibile a ogni visitatore. Chiama il tuo backend, che poi chiama LLMWebSearch.

Crea una chiave API per agente, app o teammate e revoca qualsiasi di esse dalla dashboard senza toccare le altre. Una chiave per servizio rende la cronologia della dashboard più facile da leggere e una chiave compromessa facile da revocare.

## Node.js: domande frequenti

### Esiste un pacchetto npm?

No. `fetch` è incluso in Node 18 e versioni successive, e l'API è semplice JSON su HTTPS. Le funzioni di questa pagina sono l'intera integrazione.

### Funziona su Deno, Bun o runtime edge?

Il codice usa solo `fetch`, `JSON` e `process.env`. I runtime con `fetch` funzionano dopo aver sostituito `process.env` con il loro modo di leggere i segreti.

### Come ritento le chiamate fallite?

Ritenta solo `429`, `502` e `504`, con una pausa che raddoppia ogni volta. Non ritentare mai `400`, `401` o `402`. La [pagina degli errori](https://llmwebsearch.com/it/docs/errors/) spiega perché e mostra un helper.

---

LLMWebSearch: LLMWebSearch è un'API di ricerca web per agenti AI che restituisce risultati classificati con il contenuto delle pagine principali in Markdown, pagata in criptovalute senza caricare documenti d'identità. I crediti prepagati da 1,20 USD/1K ricerche non scadono mai. Senza KYC — niente documento, niente selfie, niente documenti. Paghi in criptovalute.
