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

ipwhois-node

v1.2.1

Published

Official Node.js / TypeScript client for the ipwhois.io IP Geolocation API. Simple, dependency-free, supports single and bulk IP lookups.

Readme

ipwhois-node

npm version Node version License

Official, dependency-free Node.js / TypeScript client for the ipwhois.io IP Geolocation API.

  • ✅ Single and bulk IP lookups (IPv4 and IPv6)
  • ✅ Works with both the Free and Paid plans
  • ✅ HTTPS by default
  • ✅ Localisation, field selection, threat detection, rate info
  • ✅ Never throws — all errors returned as { success: false } objects
  • ✅ No runtime dependencies — only Node's built-in node:http / node:https
  • ✅ First-class TypeScript types
  • ✅ Node.js 18+

Installation

npm install ipwhois-node

Free vs Paid plan

The same IPWhois class is used for both plans. The only difference is whether you pass an API key:

  • Free plan — create the client without arguments. No API key, no signup required. Suitable for low-traffic and non-commercial use.
  • Paid plan — create the client with your API key from https://ipwhois.io. Higher limits, plus access to bulk lookups and threat-detection data.
import { IPWhois } from 'ipwhois-node';

const free = new IPWhois();               // Free plan — no API key
const paid = new IPWhois('YOUR_API_KEY'); // Paid plan — with API key

Everything else (lookup(), options, error handling) is identical.

Quick start — Free plan (no API key)

import { IPWhois } from 'ipwhois-node';

const ipwhois = new IPWhois(); // no API key

const info = await ipwhois.lookup('8.8.8.8');

if (info.success) {
    console.log(`${info.country} ${info.flag?.emoji}`);
    // → United States 🇺🇸

    console.log(`${info.city}, ${info.region}`);
    // → Mountain View, California
}

Quick start — Paid plan (with API key)

Get an API key at https://ipwhois.io and pass it to the constructor:

import { IPWhois } from 'ipwhois-node';

const ipwhois = new IPWhois('YOUR_API_KEY'); // with API key

const info = await ipwhois.lookup('8.8.8.8');

if (info.success) {
    console.log(`${info.country} ${info.flag?.emoji}`);
    // → United States 🇺🇸

    console.log(`${info.city}, ${info.region}`);
    // → Mountain View, California
}

ℹ️ Pass nothing to look up your own public IP: await ipwhois.lookup(); — works on both plans.

Lookup options

Every option below can be passed per call, or set once on the client as a default.

| Option | Type | Plans needed | Description | | ------------ | ---------- | -------------------- | ---------------------------------------------------------------------- | | lang | string | Free + Paid | One of: en, ru, de, es, pt-BR, fr, zh-CN, ja | | fields | string[] | Free + Paid | Restrict the response to specific fields (e.g. ['country', 'city']) | | rate | boolean | Basic and above | Include the rate block (limit, remaining) | | security | boolean | Business and above | Include the security block (proxy/vpn/tor/hosting) |

Setting defaults once

Every option can be passed two ways: per call (as the second argument to lookup() / bulkLookup()) or once as a default on the client. Per-call options always override the defaults, so it's safe to set sensible defaults and only override what differs for a specific call.

Defaults are set with fluent setters — setLanguage(), setFields(), setSecurity(), setRate(), setTimeout(), setConnectTimeout(), setUserAgent() — and can be chained:

import { IPWhois } from 'ipwhois-node';

// Free plan
const ipwhois = new IPWhois()
    .setLanguage('en')
    .setFields(['success', 'country', 'city', 'flag.emoji'])
    .setTimeout(8_000);
import { IPWhois } from 'ipwhois-node';

// Paid plan
const ipwhois = new IPWhois('YOUR_API_KEY')
    .setLanguage('en')
    .setFields(['success', 'country', 'city', 'flag.emoji'])
    .setTimeout(8_000);

Either client behaves the same way at call time — per-call options always win over the defaults:

await ipwhois.lookup('8.8.8.8');                  // uses lang=en, the field whitelist, and timeout=8000
await ipwhois.lookup('1.1.1.1', { lang: 'de' });  // overrides lang for this single call only

⚠️ When you restrict fields with setFields() (or the per-call fields option), the API only returns the fields you ask for. Always include 'success' in the list if you rely on result.success for error checking — otherwise the field will be missing on responses.

ℹ️ setSecurity(true) requires Business+ and setRate(true) requires Basic+. See the table above for what's available where.

HTTPS Encryption

By default, all requests are sent over HTTPS. If you need to disable it (for example, in environments without an up-to-date CA bundle), pass ssl: false to the constructor:

import { IPWhois } from 'ipwhois-node';

// Free plan
const ipwhois = new IPWhois(null, { ssl: false });
import { IPWhois } from 'ipwhois-node';

// Paid plan
const ipwhois = new IPWhois('YOUR_API_KEY', { ssl: false });

ℹ️ HTTPS is strongly recommended for production traffic — your API key is sent in the query string and would otherwise travel in clear text.

Bulk lookup (Paid plan only)

The bulk endpoint sends up to 100 IPs in a single GET request. Each address counts as one credit. Available on the Business and Unlimited plans.

import { IPWhois } from 'ipwhois-node';

const ipwhois = new IPWhois('YOUR_API_KEY');

const results = await ipwhois.bulkLookup([
    '8.8.8.8',
    '1.1.1.1',
    '208.67.222.222',
    '2c0f:fb50:4003::',   // IPv6 is fine — mix freely
]);

// Whole-batch failure (bad key, network down, …) is a single error object.
if (!Array.isArray(results)) {
    console.error(`Bulk failed: ${results.message}`);
    return;
}

for (const row of results) {
    if (!row.success) {
        // Per-IP errors (e.g. "Invalid IP address") are returned inline,
        // they do NOT throw — the rest of the batch is still usable.
        console.log(`skip ${row.ip}: ${row.message}`);
        continue;
    }
    console.log(`${row.ip} → ${row.country}`);
}

ℹ️ Bulk requires an API key. Calling bulkLookup() without one will fail at the API level.

Error handling

The library never throws. Every failure — invalid IP, bad API key, rate limit, network outage, bad options — comes back inside the response object with success: false and a message. Just check result.success after every call:

const info = await ipwhois.lookup('8.8.8.8');

if (!info.success) {
    console.error(`Lookup failed: ${info.message}`);
    return;
}

console.log(info.country);

This means an outage of the ipwhois.io API (or of your server's DNS, connection, etc.) will never surface as a fatal error in your application — you decide how to react. TypeScript also narrows the type for you: inside the if (info.success) branch, info is IpwhoisSuccess; in the else branch it's IpwhoisError.

Error response fields

Every error response contains success: false, a human-readable message, and an error_type so you can branch on the category of the failure. Some errors include extra fields you can branch on:

| Field | When it's present | | -------------- | -------------------------------------------------------------------------------------------- | | success | Always — false for error responses (true for successful responses) | | message | Always — human-readable description of what went wrong | | error_type | Always — one of 'api', 'network', or 'invalid_argument' | | http_status | On HTTP 4xx / 5xx responses | | retry_after | On HTTP 429 — free plan only (the paid endpoint does not send a Retry-After header) |

const info = await ipwhois.lookup('8.8.8.8');

if (!info.success) {
    if (info.http_status === 429) {
        await new Promise(r => setTimeout(r, (info.retry_after ?? 60) * 1000));
        // …retry
    }
    if (info.error_type === 'network') {
        // DNS failure, connection refused, timeout, …
    }
    console.error(`Error: ${info.message}`);
    return;
}

Response shape

A successful response includes (depending on your plan and selected options):

{
    "ip": "8.8.4.4",
    "success": true,
    "type": "IPv4",
    "continent": "North America",
    "continent_code": "NA",
    "country": "United States",
    "country_code": "US",
    "region": "California",
    "region_code": "CA",
    "city": "Mountain View",
    "latitude": 37.3860517,
    "longitude": -122.0838511,
    "is_eu": false,
    "postal": "94039",
    "calling_code": "1",
    "capital": "Washington D.C.",
    "borders": "CA,MX",
    "flag": {
        "img": "https://cdn.ipwhois.io/flags/us.svg",
        "emoji": "🇺🇸",
        "emoji_unicode": "U+1F1FA U+1F1F8"
    },
    "connection": {
        "asn": 15169,
        "org": "Google LLC",
        "isp": "Google LLC",
        "domain": "google.com"
    },
    "timezone": {
        "id": "America/Los_Angeles",
        "abbr": "PDT",
        "is_dst": true,
        "offset": -25200,
        "utc": "-07:00",
        "current_time": "2026-05-08T14:31:48-07:00"
    },
    "currency": {
        "name": "US Dollar",
        "code": "USD",
        "symbol": "$",
        "plural": "US dollars",
        "exchange_rate": 1
    },
    "security": {
        "anonymous": false,
        "proxy": false,
        "vpn": false,
        "tor": false,
        "hosting": false
    },
    "rate": {
        "limit": 250000,
        "remaining": 50155
    }
}

For the full field reference, see the official documentation.

An error response looks like:

{
    "success": false,
    "message": "Rate limit exceeded",
    "error_type": "api",       // 'api' / 'network' / 'invalid_argument'
    "http_status": 429,         // present for HTTP 4xx / 5xx
    "retry_after": 60       // additionally present on HTTP 429 — free plan only
}

TypeScript

The package ships with full type declarations. The result of lookup() is a discriminated union:

import type { IpwhoisResult, IpwhoisSuccess, IpwhoisError } from 'ipwhois-node';

const info: IpwhoisResult = await ipwhois.lookup('8.8.8.8');

if (info.success) {
    // info is IpwhoisSuccess here
    info.country;
} else {
    // info is IpwhoisError here
    info.message;
}

For bulkLookup(), narrow on Array.isArray():

const results = await ipwhois.bulkLookup(['8.8.8.8', '1.1.1.1']);

if (Array.isArray(results)) {
    // per-IP results
    for (const row of results) { /* … */ }
} else {
    // whole-batch failure: results is IpwhoisError
}

Requirements

  • Node.js 18 or newer
  • No runtime dependencies

Contributing

Issues and pull requests are welcome on GitHub.

License

MIT © ipwhois.io