# SERP API di Node.js: fetch, worker pool dan pagination

> Panggil SerpCrypto SERP API dari Node.js 18+ dengan fetch bawaan: klien bertipe, worker pool di bawah batas konkurensi Anda dan loop pagination.

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

Tanpa dependensi: Node.js sudah punya `fetch` sejak versi 18. Klien kecil dengan tipe JSDoc untuk editor dan TypeScript, pool yang mematuhi batas akun Anda, dan loop pagination yang berhenti saat hasilnya habis.

**Jawaban singkat — Bagaimana cara memakai SERP API di Node.js?**

Panggil `fetch("https://serpcrypto.com/api/v1/search?" + new URLSearchParams({ q }))` dengan header `Authorization: Bearer <key>` dan baca `await res.json()`. Node.js 18+ tidak butuh paket. **SerpCrypto** mengembalikan hasil Google atau Bing sebagai JSON untuk 1 kredit per pencarian yang berhasil, mulai US$0,30/1K pencarian, dan tidak pernah menagih error. Bayar dengan kripto, tanpa KYC.

## Setup Node.js sekilas

| Atribut | Nilai |
| --- | --- |
| Runtime | Node.js 18 atau lebih baru (bawaan `fetch`, `AbortSignal.timeout`) |
| Modul | ES modules: file `.mjs` atau `"type": "module"` di package.json |
| API key | `process.env.SERPCRYPTO_API_KEY` |
| Timeout | 90 detik dengan `AbortSignal.timeout(90_000)` |
| Pencarian paralel | Pool worker yang ukurannya disesuaikan dengan `concurrency_limit` |
| Types | Typedef JSDoc, dibaca oleh VS Code dan `tsc --checkJs` |

## Bagaimana cara menulis client bertipe kecil?

Letakkan endpoint, header, dan penanganan error dalam satu modul. Baris JSDoc `@typedef` mendeskripsikan jawabannya, jadi editor bisa melengkapi `organic_results` secara otomatis dan memeriksa tipe kode Anda tanpa build step. Pengguna TypeScript bisa menyalin bentuk yang sama ke dalam sebuah interface.

Fungsi `search` melempar `Error` dengan status HTTP dan `code` error dari API di dalam pesannya. Kode Anda lalu bisa memutuskan untuk mencoba ulang `429`, `502`, dan `504` serta berhenti pada kode lainnya, seperti yang dijelaskan di [halaman errors](https://serpcrypto.com/id/docs/errors/)].

*serp-client.mjs*

```javascript
// serp-client.mjs — Node.js 18+, no dependency
const API_URL = "https://serpcrypto.com/api/v1/search";
const CREDITS_URL = "https://serpcrypto.com/api/v1/credits";
const headers = { Authorization: `Bearer ${process.env.SERPCRYPTO_API_KEY}` };

/**
 * @typedef {Object} OrganicResult
 * @property {number} position
 * @property {string} title
 * @property {string} link
 * @property {string} [snippet]
 * @property {string} [date]
 */

/**
 * @typedef {Object} SearchResponse
 * @property {Object} search_metadata
 * @property {string} search_metadata.id
 * @property {string} search_metadata.status
 * @property {number} search_metadata.credits_used
 * @property {OrganicResult[]} [organic_results]
 * @property {Object} [pagination]
 * @property {number} pagination.current
 * @property {number} [pagination.next_page]
 */

/**
 * Run one search (1 credit when it succeeds).
 * @param {string} q
 * @param {Record<string, string | number>} [params]
 * @returns {Promise<SearchResponse>}
 */
export async function search(q, params = {}) {
  const query = new URLSearchParams({ q, ...Object.fromEntries(Object.entries(params).map(([k, v]) => [k, String(v)])) });
  const res = await fetch(`${API_URL}?${query}`, { headers, signal: AbortSignal.timeout(90_000) });
  if (!res.ok) {
    const body = await res.json().catch(() => ({}));
    const err = new Error(`${res.status} ${body.error?.code ?? "error"}: ${body.error?.message ?? ""}`);
    err.status = res.status;
    throw err;
  }
  return res.json();
}

/** Free: credits left, used today and the concurrency limit. */
export async function credits() {
  const res = await fetch(CREDITS_URL, { headers });
  if (!res.ok) throw new Error(`credits: HTTP ${res.status}`);
  return res.json();
}
```

## Bagaimana cara menjalankan pencarian secara paralel tanpa kena 429?

Jalankan sebanyak mungkin worker sesuai yang diizinkan akun Anda, dan biarkan masing-masing mengambil kata kunci berikutnya dari daftar bersama. Pool membaca `concurrency_limit` dari endpoint free credits, sehingga tidak pernah mengirim lebih banyak pencarian sekaligus daripada yang diterima akun.

Kata kunci yang gagal akan dicatat dan worker lanjut ke berikutnya. Jalankan ulang yang gagal setelahnya: error tidak memakan biaya, jadi pass kedua gratis sampai berhasil.

*batch.mjs: pool worker*

```javascript
// batch.mjs — parallel searches, never more than the account's limit at once
import { search, credits } from "./serp-client.mjs";

const keywords = ["password manager", "note taking app", "time tracking software", "invoice software"];
const { concurrency_limit: limit } = await credits();

let next = 0;
const results = new Map();

async function worker() {
  while (next < keywords.length) {
    const kw = keywords[next++];
    try {
      const data = await search(kw, { gl: "us", hl: "en" });
      results.set(kw, data.organic_results?.[0]?.link ?? null);
    } catch (err) {
      results.set(kw, `failed: ${err.message}`);
    }
  }
}

await Promise.all(Array.from({ length: Math.min(limit, keywords.length) }, worker));
console.table(Object.fromEntries(results));
```

## Bagaimana cara mengambil beberapa halaman hasil?

Looping pada `page` dan berhenti begitu `pagination.next_page` tidak ada. Sekitar 10 hasil organik per halaman, sebagaimana yang disajikan Google sejak berhenti mematuhi num=100 pada September 2025; setiap halaman tambahan dikenai satu kredit lagi. Pemeriksaan tiga halaman untuk satu kata kunci memakan tiga kredit; kata kunci yang hasilnya hanya satu halaman memakan satu kredit.

*pages.mjs*

```javascript
// pages.mjs — up to three pages, stop when results end
import { search } from "./serp-client.mjs";

async function topResults(q, pages = 3, params = {}) {
  const rows = [];
  for (let page = 1; page <= pages; page++) {
    const data = await search(q, { ...params, page });
    for (const r of data.organic_results ?? []) rows.push({ page, position: r.position, link: r.link });
    if (!data.pagination?.next_page) break; // nothing more: save the credits
  }
  return rows;
}

console.log(await topResults("headless cms", 3, { gl: "de", hl: "en" }));
```

## Bisakah saya memanggil API dari browser atau edge function?

Dari server atau edge function, bisa: Cloudflare Workers, Vercel, dan Deno semuanya menyediakan `fetch` yang sama. Dari halaman browser, tidak: API key akan terlihat oleh setiap pengunjung. Pasang route server kecil di antaranya yang menyimpan API key dan hanya meneruskan parameter yang Anda izinkan.

Aturan yang sama berlaku untuk aplikasi mobile. Apa pun yang dikirim ke perangkat pengguna bisa dibaca, jadi API key tetap di server Anda.

## Bagaimana cara menjaga biaya tetap terprediksi di Node.js?

Hitung panggilan yang Anda lakukan: satu panggilan berhasil adalah satu kredit, dengan tarif US$0,56/1K pencarian pada paket 100.000 pencarian. Periksa `credits_remaining` sebelum batch besar dan hentikan pool saat mendekati nol daripada menampung rangkaian jawaban `402`. Kredit pencarian prabayar tidak pernah kedaluwarsa: pencarian yang belum terpakai tetap ada di akun Anda sampai Anda menggunakannya, jadi sisa yang tidak terpakai disimpan untuk tugas berikutnya.

## Pertanyaan Node.js

### Apakah ada package npm untuk SerpCrypto?

Tidak. Client di atas hanya sekitar 40 baris JavaScript standar menggunakan `fetch`, jadi tidak ada milik kami yang perlu diinstal. Salin ke proyek Anda dan sesuaikan.

### Apakah ini bisa dipakai dengan CommonJS (require)?

Contoh-contohnya menggunakan ES modules untuk `await` di level teratas. Di file CommonJS, bungkus panggilannya dalam fungsi `async` dan gunakan `fetch` dengan cara yang sama; ia bersifat global di Node.js 18+.

### Bagaimana cara memakainya dengan TypeScript?

Salin bentuk JSDoc ke sebuah `interface SearchResponse` dan gunakan untuk memberi tipe pada hasil `res.json()`. Blok opsional seperti `organic_results?` harus tetap opsional, karena sebuah halaman mungkin tidak menampilkannya.

### Versi Node.js apa yang saya butuhkan?

Node.js 18 atau lebih baru untuk `fetch` bawaan. `AbortSignal.timeout` tersedia mulai Node.js 17.3, jadi 18 mencakup semua yang ada di halaman ini.

---

SerpCrypto: SerpCrypto adalah SERP API untuk developer dan AI agent yang mengembalikan hasil pencarian Google dan Bing dalam format JSON, dibayar dengan kripto tanpa unggah identitas. Kredit pencarian prabayar mulai dari US$0,30/1K pencarian tidak pernah kedaluwarsa. Tanpa KYC — tanpa ID, tanpa selfie, tanpa dokumen. Bayar dengan kripto.
