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

sxgeo-js

v1.0.0

Published

Node.js reader and updater for Sypex Geo binary databases (SxGeo.dat / SxGeoCity.dat)

Downloads

137

Readme

sxgeo-js

Node.js reader and updater for Sypex Geo binary databases.

Reads the official SxGeo.dat and SxGeoCity.dat files and resolves an IPv4 address to country code, country ID, city data, region data, and full country metadata. Also provides helper functions to download and update the database files directly from sypexgeo.net.

Disclaimer: This package is an independent reader for the open Sypex Geo binary format. It is not affiliated with or endorsed by sypexgeo.net. The database files are provided by sypexgeo.net under their own terms — please review the Sypex Geo license before using the database in your project.


Installation

npm install sxgeo-js

Download a Database

This package does not bundle a database file.

Download one from the official Sypex Geo website and place it somewhere in your project, or use the built-in helper (see Updater API):

| File | Description | | ------------------ | -------------------------------------------- | | SxGeo.dat | Country only (smaller, faster to load) | | SxGeoCity.dat | City + region + country (full dataset) |

Recommended project layout:

your-project/
  data/
    SxGeoCity.dat
  src/
    index.js

Quick Start

ESM

import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { SxGeoReader } from 'sxgeo-js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const dbPath = path.join(__dirname, 'data', 'SxGeoCity.dat');

const reader = new SxGeoReader(dbPath, SxGeoReader.MEMORY | SxGeoReader.BATCH);

console.log(reader.getCityFull('1.1.1.1'));

CommonJS

const path = require('node:path');
const { SxGeoReader } = require('sxgeo-js');

const dbPath = path.join(__dirname, 'data', 'SxGeoCity.dat');
const reader = new SxGeoReader(dbPath, SxGeoReader.MEMORY | SxGeoReader.BATCH);

console.log(reader.getCityFull('1.1.1.1'));

TypeScript

import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { SxGeoReader } from 'sxgeo-js';
import type { CityFullResult } from 'sxgeo-js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const reader = new SxGeoReader(
  path.join(__dirname, 'data', 'SxGeoCity.dat'),
  SxGeoReader.MEMORY | SxGeoReader.BATCH,
);

const result: CityFullResult | null = reader.getCityFull('1.1.1.1');
console.log(result);

Constructor

const reader = new SxGeoReader(databasePath: string, mode?: number);

| Parameter | Type | Description | | -------------- | -------- | -------------------------------------------------------- | | databasePath | string | Absolute or relative path to SxGeo.dat / SxGeoCity.dat | | mode | number | Optional bitmask of read modes (see table below) |

Mode flags

| Flag | Value | Description | | --------------------- | ----- | -------------------------------------------------------- | | SxGeoReader.FILE | 0 | Read from disk on every lookup (lowest memory use) | | SxGeoReader.MEMORY | 1 | Pre-load data section into RAM | | SxGeoReader.BATCH | 2 | Pre-load index buffers into RAM for faster repeated lookups |

Combine flags with bitwise OR:

// Best throughput, highest memory use
const reader = new SxGeoReader(dbPath, SxGeoReader.MEMORY | SxGeoReader.BATCH);

// Minimal memory, reads everything from disk
const reader = new SxGeoReader(dbPath, SxGeoReader.FILE);

Reader API

get(ip)

Returns getCity(ip) when a city database is open, or getCountry(ip) when a country database is open.

reader.get('8.8.8.8');
// City DB  → { city: {...}, country: { id: 225, iso: 'US' } }
// Country DB → 'US'

getCountry(ip)

Returns the ISO 3166-1 alpha-2 country code, or null for private/unknown IPs.

reader.getCountry('8.8.8.8');
// 'US'

getCountryId(ip)

Returns the numeric Sypex Geo country ID, or null if not found.

reader.getCountryId('8.8.8.8');
// 225

getCity(ip)

Returns a short city result (city + country ID and ISO), or null.

reader.getCity('1.1.1.1');
{
  "city": {
    "id": 2174003,
    "lat": -27.46794,
    "lon": 153.02809,
    "name_ru": "Брисбен",
    "name_en": "Brisbane"
  },
  "country": {
    "id": 16,
    "iso": "AU"
  }
}

getCityFull(ip)

Returns city, region, and full country data, or null.

reader.getCityFull('1.1.1.1');
{
  "city": {
    "id": 2174003,
    "lat": -27.46794,
    "lon": 153.02809,
    "name_ru": "Брисбен",
    "name_en": "Brisbane"
  },
  "region": {
    "id": 2152274,
    "name_ru": "Квинсленд",
    "name_en": "State of Queensland",
    "iso": "AU-QLD"
  },
  "country": {
    "id": 16,
    "iso": "AU",
    "lat": -25,
    "lon": 135,
    "name_ru": "Австралия",
    "name_en": "Australia"
  }
}

about()

Returns metadata about the open database file.

reader.about();
{
  "created": "2026.01.19",
  "timestamp": 1768861974,
  "charset": "utf-8",
  "type": "SxGeo City EN",
  "byteIndex": 224,
  "mainIndex": 1775,
  "blocksInIndexItem": 3376,
  "ipBlocks": 5995209,
  "blockSize": 6,
  "city":    { "maxLength": 127, "totalSize": 2687625 },
  "region":  { "maxLength": 175, "totalSize": 109649  },
  "country": { "maxLength": 147, "totalSize": 9387    }
}

close()

Releases the underlying file descriptor. Call this when the reader is no longer needed (e.g. at the end of a short-lived script).

reader.close();

In long-running processes (web servers) you typically keep one reader open for the lifetime of the process, so calling close() is optional.


Updater API

import {
  downloadCityDatabase,
  downloadCountryDatabase,
  checkCityDatabaseUpdate,
  checkCountryDatabaseUpdate,
  DatabaseAlreadyExistsError,
} from 'sxgeo-js';

downloadCityDatabase(destDir, options?)

Downloads SxGeoCity.dat from sypexgeo.net and extracts it into destDir.

// First-time download
const filePath = await downloadCityDatabase('./data');
console.log(`Saved to: ${filePath}`);

// Force re-download (overwrites existing file)
await downloadCityDatabase('./data', { force: true });

// Smart update — skips the download if the server has not changed
await downloadCityDatabase('./data', { skipIfCurrent: true });

Returns the absolute path of the extracted .dat file.

Throws DatabaseAlreadyExistsError if the file already exists and neither force nor skipIfCurrent is set.


downloadCountryDatabase(destDir, options?)

Downloads SxGeo.dat (country-only) and extracts it into destDir. Accepts the same options as downloadCityDatabase.

const filePath = await downloadCountryDatabase('./data');
console.log(`Saved to: ${filePath}`);

Download options

| Option | Type | Default | Description | | ---------------- | --------- | ------- | ----------- | | force | boolean | false | Overwrite the existing file without any check | | skipIfCurrent | boolean | false | Check the server headers first; skip the download if nothing has changed |

When skipIfCurrent is true, the function sends a single HTTP HEAD request (no body downloaded) and reads only the first 8 bytes of the local .dat file. If both the server's Last-Modified date and Content-Length match the values recorded at the previous download, the large ZIP file is not fetched.


checkCityDatabaseUpdate(destDir)

Checks whether a newer SxGeoCity.dat is available without downloading anything. Sends one HTTP HEAD request and reads a tiny sidecar file (see Sidecar metadata below).

import { checkCityDatabaseUpdate } from 'sxgeo-js';

const status = await checkCityDatabaseUpdate('./data');
console.log(status);
{
  "upToDate": true,
  "localTimestamp": 1769893012,
  "localCreated": "2026.01.31",
  "localFileMtime": "2026-04-11T22:23:42.505Z",
  "knownLastModified": "2026-03-31T21:44:42.000Z",
  "knownRemoteSize": 22220636,
  "serverLastModified": "2026-03-31T21:44:42.000Z",
  "remoteSize": 22220636
}

| Field | Description | | ------------------- | ----------- | | upToDate | true when the server has not changed since the last download | | localTimestamp | Unix timestamp embedded in the .dat header — the date the geo data was compiled (may differ from the ZIP publish date) | | localCreated | localTimestamp formatted as YYYY.MM.DD | | localFileMtime | When the local .dat file was last written to disk (download date) | | knownLastModified | Last-Modified recorded in the sidecar at the last download | | knownRemoteSize | Content-Length recorded in the sidecar at the last download | | serverLastModified| Last-Modified reported by the server right now | | remoteSize | Content-Length reported by the server right now |


checkCountryDatabaseUpdate(destDir)

Same as checkCityDatabaseUpdate but for SxGeo.dat.


DatabaseAlreadyExistsError

Thrown by downloadCityDatabase / downloadCountryDatabase when the target file exists and neither force nor skipIfCurrent is set.

import { downloadCityDatabase, DatabaseAlreadyExistsError } from 'sxgeo-js';

try {
  await downloadCityDatabase('./data');
} catch (err) {
  if (err instanceof DatabaseAlreadyExistsError) {
    console.log(`Already present: ${err.filePath}`);
    // Re-download only if the server has a newer version:
    await downloadCityDatabase('./data', { skipIfCurrent: true });
  } else {
    throw err;
  }
}

Sidecar metadata

Every time a database is successfully downloaded, a small JSON file is written alongside the .dat file:

data/
  SxGeoCity.dat
  SxGeoCity.dat.meta.json   ← written automatically
{
  "downloadedAt": "2026-04-11T22:23:42.505Z",
  "remoteLastModified": "2026-03-31T21:44:42.000Z",
  "remoteSize": 22220636
}

This file records the server's Last-Modified header and Content-Length at the time of the download. Future freshness checks compare both values against the live server headers — catching updates even when only the file content changes but the publish date stays the same (or vice versa).

The sidecar file is a runtime artifact. Add *.dat.meta.json to your .gitignore (the package itself already does this in its own repo).


Scheduled Updates

The free Sypex Geo database is updated periodically. The recommended pattern for a cron job or scheduled task:

// update-db.mjs  — run with: node update-db.mjs
import { downloadCityDatabase } from 'sxgeo-js';

// Downloads only when the server has published a new version.
// A single HEAD request is sent first; the large ZIP is skipped if up to date.
const filePath = await downloadCityDatabase('./data', { skipIfCurrent: true });
console.log(`Database is current: ${filePath}`);

To check without downloading at all (e.g. in a status dashboard):

import { checkCityDatabaseUpdate } from 'sxgeo-js';

const { upToDate, localCreated, serverLastModified } =
  await checkCityDatabaseUpdate('./data');

if (!upToDate) {
  console.log(
    `Update available — server: ${serverLastModified?.toDateString()}, ` +
    `local geo data compiled: ${localCreated}`,
  );
}

Notes

  • Only IPv4 lookups are supported.
  • Private, loopback, and reserved ranges (10.x.x.x, 127.x.x.x, 0.x.x.x and addresses with a first octet ≥ the database's byte-index size) return null.
  • SxGeoCity.dat already contains full country data, so a single file is enough for city-level lookups.
  • If you only need country codes, SxGeo.dat is significantly smaller.

Development

# Install dependencies
npm install

# Build (ESM + CJS + type declarations)
npm run build

# Run tests
npm test

License

MIT — see LICENSE.

Database files are provided by sypexgeo.net under their own license. This package is an independent implementation that reads the open Sypex Geo binary format.