# Contoh Node.js: pencarian dengan fetch, pool kecil, dan jawaban bersitasi

> Contoh Node.js web search API: fetch, pool konkurensi kecil, loop jawaban dengan sitasi untuk model kompatibel OpenAI. Tanpa KYC, bayar dengan kripto.

- URL: https://llmwebsearch.com/id/docs/node/
- Terakhir diperbarui: 2026-10-22
- Penerbit: LLMWebSearch (https://llmwebsearch.com)

Node 18 dan yang lebih baru sudah menyertakan `fetch`, jadi API ini tidak butuh package sama sekali. Halaman ini menunjukkan satu pencarian, pool yang menjalankan beberapa pencarian tanpa membanjiri rate limit Anda, dan loop yang mengubah sumber menjadi jawaban dengan sitasi bernomor.

**Jawaban singkat — Bagaimana cara memanggil web search API untuk AI agents dari Node.js?**

Gunakan `fetch` bawaan Node 18+ untuk POST kueri Anda ke `https://llmwebsearch.com/api/v1/search` dengan key Bearer. **LLMWebSearch** menjawab dengan hasil berperingkat dan 3 halaman teratas sebagai Markdown, siap diberi nomor dan diteruskan ke model untuk jawaban bersitasi. Satu pencarian memakai 1 kredit, mulai dari US$1,20/1K pencarian per Oktober 2026. Tidak perlu package npm. Tanpa KYC; bayar dengan kripto.

## Yang Anda butuhkan

| Atribut | Nilai |
| --- | --- |
| Node.js | 18 atau lebih baru, untuk `fetch` bawaan; simpan skrip sebagai `.mjs` untuk `await` tingkat atas |
| Paket | Tidak ada |
| Key | `LLMWEBSEARCH_API_KEY` di environment Anda |
| Untuk loop jawaban | Endpoint chat yang kompatibel dengan OpenAI: `LLM_BASE_URL`, `LLM_API_KEY`, `LLM_MODEL` |
| Penagihan | Satu kredit membayar satu pencarian: hingga 20 hasil berperingkat, dengan halaman dari 3 hasil teratas dibaca dan dikembalikan sebagai Markdown. Membaca 4 hingga 10 halaman menjadi 2 kredit. Panggilan yang berakhir dengan error tidak dikenakan biaya |

## Bagaimana cara menulis fungsi pencarian yang dapat digunakan ulang?

Bungkus panggilan sekali dan gunakan ulang di mana saja. Fungsi di bawah mengirim body, melempar error yang mudah dibaca pada setiap kegagalan HTTP, dan mengembalikan JSON yang telah di-parse. Error membawa `code` milik API itu sendiri, seperti `rate_limited` atau `insufficient_credits`, sehingga pemanggil dapat memutuskan apa yang harus dilakukan.

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

```

## Bagaimana cara menjalankan beberapa pencarian tanpa kena rate limit?

Jalankan lewat pool kecil: sejumlah worker tetap yang masing-masing mengambil kueri berikutnya dari daftar bersama. Empat worker adalah titik awal yang masuk akal. Setiap akun memiliki batas panggilan per menit yang ditampilkan di dashboard; hubungi support dan batas itu bisa dinaikkan.

Pool di bawah ini tidak butuh library. Setiap worker menyimpan hasilnya sendiri, dan pencarian yang gagal dicatat beserta error-nya alih-alih menghentikan proses. Itu penting untuk agen riset, di mana satu sub-pertanyaan yang buruk tidak boleh mengorbankan seluruh tugas.

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

```

## Bagaimana cara mengubah hasil menjadi jawaban dengan sitasi?

Beri nomor pada sumber, letakkan URL di samping masing-masing, pangkas sesuai anggaran, dan minta model mengutip berdasarkan nomor. API mengembalikan sumber, bukan jawaban tertulis: model Anda sendiri yang membaca halaman, menulis balasan, dan mengutip URL-nya. Skrip di bawah ini berbicara ke endpoint `chat/completions` apa pun yang kompatibel dengan OpenAI, dipilih lewat tiga environment variable, sehingga berfungsi baik dengan model hosted maupun lokal.

Anggaran sekitar 24.000 karakter, kira-kira 6.000 token, dibagi rata di antara sumber-sumber. Hasil yang halamannya tidak dapat dibaca dikirim sebagai snippet-nya, dengan penanda demikian.

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

```

## Sebaiknya saya pakai GET atau POST dari Node?

POST, dengan body JSON, di kode aplikasi. Array seperti `include_domains` dan pertanyaan panjang lebih rapi dikirim lewat body daripada query string. GET tersedia untuk uji cepat dan untuk alat yang tidak bisa mengirim body; bangun query string-nya dengan `URLSearchParams` dan gabungkan daftar dengan koma.

Kedua bentuk berbiaya sama dan mengembalikan JSON yang sama. [Referensi pencarian](https://llmwebsearch.com/id/docs/search/) mencantumkan setiap parameter untuk keduanya.

## Di mana sebaiknya API key disimpan?

Di environment variable yang dibaca saat runtime, jangan pernah di file sumber. Di server, gunakan secret store platform Anda. Di browser, jangan panggil API sama sekali: API key akan terlihat oleh setiap pengunjung. Panggil backend Anda sendiri, yang kemudian memanggil LLMWebSearch.

Buat satu API key per agen, aplikasi, atau rekan tim dan cabut salah satunya dari dashboard tanpa mengganggu yang lain. Satu API key per layanan membuat riwayat dashboard lebih mudah dibaca, dan API key yang bocor mudah dicabut.

## Node.js: pertanyaan umum

### Apakah ada paket npm?

Tidak. `fetch` sudah built-in di Node 18 dan yang lebih baru, dan API-nya adalah JSON biasa lewat HTTPS. Fungsi-fungsi di halaman ini adalah keseluruhan integrasinya.

### Apakah berfungsi di Deno, Bun, atau edge runtime?

Kode ini hanya memakai `fetch`, `JSON`, dan `process.env`. Runtime dengan `fetch` berfungsi setelah mengganti `process.env` dengan cara mereka sendiri untuk membaca secret.

### Bagaimana cara mengulang panggilan yang gagal?

Ulangi hanya `429`, `502`, dan `504`, dengan jeda yang berlipat dua setiap kali. Jangan pernah mengulang `400`, `401`, atau `402`. [Halaman error](https://llmwebsearch.com/id/docs/errors/) menjelaskan alasannya dan menunjukkan helper.

---

LLMWebSearch: LLMWebSearch adalah API pencarian web untuk agen AI yang mengembalikan hasil berperingkat beserta konten halaman teratas dalam Markdown, dibayar dengan kripto tanpa unggah ID. Kredit prabayar mulai dari US$1,20/1K pencarian tidak pernah kedaluwarsa. Tanpa KYC — tanpa ID, tanpa selfie, tanpa dokumen. Bayar dengan kripto.
