DEV Community

dodou
dodou

Posted on

Get Google Search Results in Node.js with a SERP API

Most "scrape Google" tutorials are Python. Fine, but half the products I touch are Node. This post is the Node.js version: one POST request, clean JSON back, no cheerio, no puppeteer.

Why not scrape Google's HTML directly? Because you'll spend your time fighting CAPTCHAs, consent walls, and weekly markup changes instead of building. A SERP API hands you the parsed results so your code stays boring and stable.

The request

The API I'm using here is SerpBase (https://api.serpbase.dev). Auth is a plain X-API-Key header, every endpoint takes a POST JSON body, and it runs Google Search, Images, News, Videos, and Maps behind one key. Node 18+ has global fetch, so there's no dependency to install:

const API_KEY = "your_api_key";   // from https://serpbase.dev/register
const BASE = "https://api.serpbase.dev";

async function googleSearch(query, { hl = "en", gl = "us", page = 1 } = {}) {
  const resp = await fetch(`${BASE}/google/search`, {
    method: "POST",
    headers: { "Content-Type": "application/json", "X-API-Key": API_KEY },
    body: JSON.stringify({ q: query, hl, gl, page, device: "default" }),
  });
  if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
  return resp.json();
}

const data = await googleSearch("nodejs async");
console.log(data.organic?.[0]);
Enter fullscreen mode Exit fullscreen mode

That's the whole call. Two headers, a JSON body, and you get the SERP back.

What comes back

Every SerpBase endpoint returns the same top-level envelope, so logging and error handling stay consistent:

{
  "status": 0,
  "request_id": "req_01Hxxxx",
  "elapsed_ms": 1420,
  "credits_charged": 1,
  "search_type": "search",
  "query": "nodejs async",
  "page": 1,
  "organic": [
    {
      "rank": 1,
      "title": "Node.js v22.x Documentation: Async Hooks",
      "link": "https://nodejs.org/api/async_hooks.html",
      "display_url": "nodejs.org",
      "snippet": "The node:async_hooks module provides an API..."
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Key fields:

  • status0 means success; anything else is an error, also returned as JSON.
  • elapsed_ms — gateway latency, handy for logging.
  • credits_charged — what the request cost after refund logic.
  • organic — the results array with rank, title, link, display_url, snippet.

When Google shows them, the response also carries featured_snippet, people_also_ask, knowledge_graph, and related_searches — all already parsed.

Mapping it to a clean array

A common task: "give me the top 10 results as a plain list." One map does it:

const top10 = (data.organic ?? []).slice(0, 10).map((r) => ({
  rank: r.rank,
  title: r.title,
  url: r.link,
  snippet: r.snippet ?? "",
}));

console.table(top10);
Enter fullscreen mode Exit fullscreen mode

Handling the fields that are optional

Not every result has every field — snippet, display_url, and sitelinks can be absent. Always use ?. and ?? rather than assuming:

const first = data.organic?.[0];
if (first) {
  const snippet = first.snippet ?? "(no snippet)";
  const sitelinks = first.sitelinks?.length ?? 0;
  console.log(`${first.title}${sitelinks} sitelinks`);
}
Enter fullscreen mode Exit fullscreen mode

This keeps the parser from throwing on a lean response.

Cost before you commit

SERP requests are metered per successful call. On SerpBase, /google/search, /google/news, and /google/videos cost 1 credit each; /google/images and both Maps endpoints cost 2. Failed dispatches and upstream timeouts are refunded automatically, so retries don't silently drain your balance.

For a side project, the pricing is refreshing: 100 free searches on signup (no card), and a $3 Starter Boost gets you 10,000 searches at the lowest rate. Standard packs start at $10 for 20k and never expire.

A tiny CLI wrapper

To make it reusable, wrap it in a one-liner CLI:

// search.mjs
const [,, query = "serp api"] = process.argv;
const data = await googleSearch(query);
for (const r of data.organic ?? []) {
  console.log(`${r.rank}. ${r.title}\n   ${r.link}`);
}
Enter fullscreen mode Exit fullscreen mode
node search.mjs "nodejs async"
Enter fullscreen mode Exit fullscreen mode

Where to go next

  • Swap hl/gl to simulate other markets: { hl: "de", gl: "de" } returns German Google.
  • Read the endpoint reference in the SerpBase documentation for Images, News, Videos, and Maps.
  • If you want the agent route, SerpBase also ships an MCP server and a portable skill for Claude, Codex, Cursor, and opencode.

Run one request against your own key and inspect organic — the whole loop is shorter than the cheerio selector you were about to write.

Top comments (0)