npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

searchxng-lib

v2.0.9

Published

Aggregate web search results from Bing, DuckDuckGo, and more — with deduplication, ranking, caching, and URL fetching. Zero-config, no API key needed.

Readme

searchxng-lib

A lightweight, production-ready Node.js library for aggregating web search results from multiple engines — with built-in deduplication, ranking, caching, and URL content fetching.

Features

  • Multi-engine search — Bing, DuckDuckGo, Brave Search API
  • Works from any IP — no server/datacenter blocks on default engines
  • Deduplication & ranking — results found by multiple engines score higher
  • Parallel fetching — all engines run simultaneously with per-engine timeouts
  • In-memory caching — configurable TTL to avoid redundant requests
  • URL content fetching — parse text, title, and metadata from any page
  • Zero-config — works out of the box with no API keys
  • TypeScript — full type definitions included
  • Dual exports — ESM (import) and CommonJS (require)
  • No heavy dependencies — only cheerio for HTML parsing; uses native fetch

Requirements

Node.js >= 18.0.0 (uses native fetch and AbortSignal)

Installation

npm install searchxng-lib

Quick Start

import { search } from 'searchxng-lib';

const results = await search('nodejs best practices');
console.log(results.results);   // array of AggregatedResult
console.log(results.fromCache); // false (first call)

Engines

| Engine | Key needed | Works from server IP | Notes | |---|---|---|---| | bing | No | ✅ Yes | Default. Reliable HTML scraper. | | duckduckgo | No | ✅ Yes | Default. Uses vqd session token. | | brave-api | Yes | ✅ Yes | Most reliable. 2,000 free req/month. | | brave | No | ⚠️ No | Blocked by Cloudflare on cloud IPs. Local only. | | swisscows | No | ⚠️ No | Blocked on cloud IPs. Local only. |

API

search(query, options?)

const results = await search('typescript tutorial', {
  engines:    ['bing', 'duckduckgo'],  // default
  timeout:    8000,                     // ms per engine, default 8000
  maxResults: 20,                       // default 20
  cacheTTL:   600,                      // seconds, default 600
  apiKeys: {
    brave: 'YOUR_BRAVE_API_KEY',        // only needed for 'brave-api' engine
  },
});

Returns: Promise<SearchResponse>

interface SearchResponse {
  results:   AggregatedResult[];
  fromCache: boolean;
  query:     string;
  timestamp: number;
}

interface AggregatedResult {
  title:   string;
  url:     string;
  snippet: string;
  engines: string[];  // which engines returned this result
  score:   number;    // how many engines returned it (higher = more relevant)
}

fetchUrl(url, timeout?)

Fetch and parse a single URL into structured text.

const page = await fetchUrl('https://example.com');
console.log(page.title);       // page title
console.log(page.description); // meta description
console.log(page.text);        // plain text, first 5000 chars
console.log(page.content);     // raw HTML
console.log(page.statusCode);  // 200

fetchUrls(urls, timeout?)

Batch fetch multiple URLs in parallel. Failed URLs are silently skipped.

const pages = await fetchUrls([
  'https://example.com',
  'https://example.org',
]);
pages.forEach(p => console.log(p.title));

initialize(options?)

Set global defaults applied to every search() call.

import { initialize } from 'searchxng-lib';

initialize({
  engines:  ['bing', 'duckduckgo'],
  cacheTTL: 1800, // 30 minutes
});

clearCache() / getCacheSize()

import { clearCache, getCacheSize } from 'searchxng-lib';

console.log(getCacheSize()); // 3
clearCache();
console.log(getCacheSize()); // 0

Examples

Zero-config

import { search } from 'searchxng-lib';

const { results } = await search('what is typescript');
results.forEach(r => {
  console.log(`[score:${r.score}] ${r.title}`);
  console.log(`  ${r.url}`);
});

With Brave Search API (most reliable)

import { search } from 'searchxng-lib';

const results = await search('nodejs event loop', {
  engines: ['bing', 'duckduckgo', 'brave-api'],
  apiKeys: { brave: 'BSA_YOUR_KEY_HERE' },
  maxResults: 10,
});

Get a free Brave API key at https://api.search.brave.com

AI assistant integration

import { search, fetchUrls } from 'searchxng-lib';

async function searchAndFetch(query) {
  const { results } = await search(query, { maxResults: 5 });
  const pages = await fetchUrls(results.slice(0, 3).map(r => r.url));

  // Ready to pass to an LLM
  return results.map((result, i) => ({
    title:    result.title,
    url:      result.url,
    snippet:  result.snippet,
    score:    result.score,
    engines:  result.engines,
    fullText: pages[i]?.text ?? null,
  }));
}

const context = await searchAndFetch('nodejs best practices');
console.log(JSON.stringify(context, null, 2));

Caching

import { search, clearCache, getCacheSize } from 'searchxng-lib';

const r1 = await search('javascript');
console.log(r1.fromCache); // false

const r2 = await search('javascript');
console.log(r2.fromCache); // true — served from cache

clearCache();

CommonJS

const { search, fetchUrl } = require('searchxng-lib');

const results = await search('javascript');
console.log(results.results);

Result Scoring

Results are scored by how many engines return them:

| Score | Meaning | |---|---| | 1 | Found by one engine | | 2 | Found by two engines — higher relevance | | 3 | Found by all three engines — highest relevance |

Results are sorted by score descending automatically.

Error Handling

All engine errors are isolated — if one engine fails or times out, the rest continue.

try {
  const results = await search('query', { timeout: 5000 });
  console.log(`Found ${results.results.length} results`);
} catch (err) {
  console.error('Search failed:', err.message);
}

License

MIT