# Node.js에서의 SERP API: fetch, worker pool, 페이지네이션

> Node.js 18+에서 내장 fetch로 SerpCrypto SERP API를 호출하세요: 타입이 지정된 클라이언트, 동시성 한도 내의 worker pool, 페이지네이션 루프까지.

- URL: https://serpcrypto.com/ko/docs/node/
- 마지막 업데이트: 2026-10-22
- 발행자: SerpCrypto (https://serpcrypto.com)

의존성 없음: Node.js에는 버전 18부터 `fetch`가 내장되어 있습니다. 편집기와 TypeScript용 JSDoc 타입이 있는 작은 클라이언트, 계정 한도를 지키는 pool, 결과가 떨어지면 멈추는 페이지네이션 루프를 제공합니다.

**빠른 답변 — Node.js에서 SERP API를 어떻게 쓰나요?**

헤더 `Authorization: Bearer <key>`와 함께 `fetch("https://serpcrypto.com/api/v1/search?" + new URLSearchParams({ q }))`을 호출하고 `await res.json()`를 읽으세요. Node.js 18+에서는 패키지가 필요 없습니다. **SerpCrypto**은 성공한 검색당 1 크레딧으로 Google 또는 Bing 결과를 JSON으로 반환하며, US$0.30/1K 검색부터 시작하고 오류는 절대 청구하지 않습니다. 암호화폐 결제, KYC 없이.

## 한눈에 보는 Node.js 설정

| 속성 | 값 |
| --- | --- |
| 런타임 | Node.js 18 이상 (내장 `fetch`, `AbortSignal.timeout`) |
| 모듈 | ES modules: `.mjs` 파일 또는 package.json의 `"type": "module"` |
| 키 | `process.env.SERPCRYPTO_API_KEY` |
| 타임아웃 | `AbortSignal.timeout(90_000)` 기준 90초 |
| 병렬 검색 | `concurrency_limit`에 맞춘 풀 크기 |
| 타입 | JSDoc typedef로, VS Code와 `tsc --checkJs`가 읽습니다 |

## 작은 타입 지정 클라이언트는 어떻게 작성하나요?

endpoint, header, 에러 처리를 하나의 모듈에 넣습니다. JSDoc `@typedef` 줄이 응답을 설명하므로, 에디터가 `organic_results`를 자동완성하고 빌드 단계 없이 코드를 타입 검사합니다. TypeScript 사용자는 동일한 형태를 interface에 복사할 수 있습니다.

`search` 함수는 HTTP 상태와 API의 에러 `code`를 메시지에 담아 `Error`를 던집니다. 그러면 코드에서 `429`, `502`, `504`은 재시도하고 나머지는 중단하도록 결정할 수 있습니다. [errors 페이지](https://serpcrypto.com/ko/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();
}
```

## 429를 맞지 않고 검색을 병렬로 실행하려면 어떻게 하나요?

계정이 허용하는 만큼 워커를 시작하고, 각 워커가 공유 목록에서 다음 키워드를 가져가게 하세요. 풀은 무료 크레딧 endpoint에서 `concurrency_limit`를 읽으므로, 계정이 허용하는 것보다 더 많은 검색을 동시에 보내지 않습니다.

실패한 키워드는 기록되고 워커는 넘어갑니다. 나중에 실패 항목을 다시 실행하세요. 에러는 비용이 들지 않으므로, 성공할 때까지 두 번째 실행은 무료입니다.

*batch.mjs: 워커 풀*

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

## 여러 페이지의 결과를 어떻게 가져오나요?

`page`을 반복하면서 `pagination.next_page`가 없으면 중단합니다. 페이지당 약 10개의 유기적 결과로, Google이 2025년 9월 num=100을 더 이상 지원하지 않게 된 이후 제공되는 방식입니다. 추가 페이지마다 크레딧이 1개 더 차감됩니다. 한 키워드를 3페이지 확인하는 데 크레딧 3개가 들고, 결과가 1페이지뿐인 키워드는 크레딧 1개가 듭니다.

*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" }));
```

## 브라우저나 edge function에서 API를 호출할 수 있나요?

서버나 edge function에서는 가능합니다. Cloudflare Workers, Vercel, Deno 모두 동일한 `fetch`를 제공합니다. 브라우저 페이지에서는 안 됩니다. 키가 모든 방문자에게 노출되기 때문입니다. 키를 보관하고 허용한 파라미터만 전달하는 작은 서버 라우트를 중간에 두세요.

모바일 앱에도 같은 규칙이 적용됩니다. 사용자 기기로 전달된 것은 무엇이든 읽을 수 있으므로 키는 서버에 두세요.

## Node.js에서 비용을 예측 가능하게 유지하려면 어떻게 하나요?

호출 수를 세세요. 성공한 호출 1건이 크레딧 1개이고, 검색 100,000회 팩에서 US$0.56/1K 검색입니다. 대량 배치 전에 `credits_remaining`를 확인하고, `402` 응답이 연속으로 쌓이게 두지 말고 0에 가까워지면 풀을 중단하세요. 선불 검색 크레딧은 만료되지 않습니다: 사용하지 않은 검색은 사용할 때까지 계정에 남아 있습니다이므로 사용하지 않은 잔여분은 다음 작업을 위해 유지됩니다.

## Node.js 관련 질문

### SerpCrypto용 npm 패키지가 있나요?

없습니다. 위 클라이언트는 `fetch`를 사용하는 약 40줄의 표준 JavaScript이므로 설치할 것이 없습니다. 프로젝트에 복사해서 맞게 수정하세요.

### CommonJS(require)에서도 작동하나요?

예제는 최상위 `await`를 위해 ES modules를 사용합니다. CommonJS 파일에서는 호출을 `async` 함수로 감싸고 `fetch`를 같은 방식으로 사용하세요. Node.js 18+에서는 전역입니다.

### TypeScript에서 사용하려면 어떻게 하나요?

JSDoc 형태를 `interface SearchResponse`에 복사하고 `res.json()`의 결과에 타입을 지정하세요. `organic_results?` 같은 선택적 블록은 선택적으로 유지해야 합니다. 페이지에 표시되지 않을 수 있기 때문입니다.

### 어떤 Node.js 버전이 필요한가요?

내장 `fetch`를 위해서는 Node.js 18 이상이 필요합니다. `AbortSignal.timeout`은 Node.js 17.3부터 사용할 수 있으므로 18이면 이 페이지의 모든 것을 커버합니다.

---

SerpCrypto: SerpCrypto는 개발자와 AI 에이전트를 위한 SERP API로, Google 및 Bing 검색 결과를 JSON으로 반환하며 신분증 업로드 없이 암호화폐로 결제합니다. 선불 검색 크레딧 US$0.30/1K 검색는 만료되지 않습니다. KYC 없이 — 신분증도, 셀피도, 서류도 없습니다. 암호화폐 결제.
