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

@jpzip/jpzip

v0.1.2

Published

Japan postal-code dataset SDK — fetches normalized JSON from jpzip.nadai.dev

Downloads

432

Readme

jpzip-js

npm version types: included License: MIT Test

TypeScript / JavaScript SDK for jpzip — a free, unlimited Japanese postal code (郵便番号) API. 日本の全郵便番号 120,677 件を CDN 配信 JSON から引く TypeScript SDK。

English | 日本語

@jpzip/jpzip looks up Japanese postal codes (郵便番号) from jpzip.nadai.dev, a CDN-hosted dataset built from Japan Post's KEN_ALL.csv and KEN_ALL_ROME.csv normalized to JSON. No registration, no rate limits, no API key.

  • 🇯🇵 Complete dataset — 120,677 entries with kanji, kana, romaji, and government codes (JIS X 0401 / 総務省地方公共団体コード)
  • ⚡️ Fast — L1 LRU + optional L2 persistent cache; preload to serve lookups without per-request network round-trips
  • 🛡️ Resilient — up to 3 attempts with exponential backoff on 5xx / network failures
  • 🪶 Zero runtime deps — uses the platform fetch only
  • 🧰 Typed end to end — first-class TypeScript, ESM + CJS dual build
  • 🆓 Free forever — backed by Cloudflare Pages' free tier (no billing axis exists)
  • 🔌 Drop-in — same API surface across every jpzip SDK

Requirements

Node.js 18+ (or any runtime with a global fetch — Bun, Deno, modern browsers, Cloudflare Workers, Vercel Edge).

Install

npm install @jpzip/jpzip
# or
pnpm add @jpzip/jpzip
# or
yarn add @jpzip/jpzip

Quick Start

import { lookup } from '@jpzip/jpzip';

const entry = await lookup('2310017');
if (entry === null) {
  console.log('not found');
} else {
  console.log(entry.prefecture, entry.city, entry.towns[0].town);
  // Output: 神奈川県 横浜市中区 港町
}

Romaji and government codes are included on the same entry:

console.log(entry.prefecture_roma, entry.city_roma, entry.towns[0].roma);
// Output: Kanagawa Ken Yokohama Shi Naka Ku Minatocho

console.log(entry.prefecture_code, entry.city_code);
// Output: 14 14104

Use Cases

Zipcode lookup HTTP endpoint (Hono)

import { Hono } from 'hono';
import { lookup } from '@jpzip/jpzip';

const app = new Hono();

app.get('/api/zipcode/:code', async (c) => {
  const entry = await lookup(c.req.param('code'));
  if (entry === null) return c.notFound();
  return c.json(entry);
});

export default app;

Works unchanged on Node, Bun, Cloudflare Workers, and Vercel Edge.

Zipcode lookup HTTP endpoint (Express)

import express from 'express';
import { lookup } from '@jpzip/jpzip';

const app = express();

app.get('/api/zipcode/:code', async (req, res, next) => {
  try {
    const entry = await lookup(req.params.code);
    if (entry === null) return res.status(404).end();
    res.json(entry);
  } catch (err) {
    next(err);
  }
});

app.listen(3000);

Batch validation

import { lookupAll } from '@jpzip/jpzip';

const all = await lookupAll(); // entire dataset in memory (~37 MiB JSON)
for (const zip of csvZipcodes) {
  if (!(zip in all)) {
    console.warn(`invalid zipcode: ${zip}`);
  }
}

Serve lookups from cache (BYO L2 backend)

The dataset is partitioned into 948 three-digit prefix buckets. The default L1 (100 entries) keeps the hottest buckets; to cache the whole dataset, pair preload({ scope: 'all' }) with an L2 cache or raise memoryCacheSize above 948.

import { JpzipClient, type PersistentCache } from '@jpzip/jpzip';
import { readFile, writeFile, unlink, rm } from 'node:fs/promises';
import { join } from 'node:path';
import { createHash } from 'node:crypto';

const dir = '.jpzip-cache';
const path = (key: string) => join(dir, createHash('sha1').update(key).digest('hex'));

const fileCache: PersistentCache = {
  async get(key) {
    try {
      return await readFile(path(key));
    } catch {
      return null;
    }
  },
  async set(key, value) {
    await writeFile(path(key), value);
  },
  async delete(key) {
    await unlink(path(key)).catch(() => {});
  },
  async clear() {
    await rm(dir, { recursive: true, force: true });
  },
};

const client = new JpzipClient({
  memoryCacheSize: 1024,
  cache: fileCache,
});

await client.preload({ scope: 'all' });
// Subsequent lookups are served from L1/L2 without hitting the network.
const entry = await client.lookup('2310017');

API Reference

Functions (module-level, share a default JpzipClient singleton)

| Function | Description | |---|---| | lookup(zipcode) | Look up a single 7-digit zipcode. Returns null for not-found or malformed input (no network call for malformed input). | | lookupGroup(prefix) | Look up by 1-, 2-, or 3-digit prefix. 1-digit fetches /g/{d}.json; 3-digit fetches /p/{ddd}.json; 2-digit fans out into 10 parallel 3-digit fetches and merges. Throws on non-digit input. | | lookupAll() | Fetch entire dataset (120k entries, ~37 MiB) in parallel across /g/0..9.json. | | getMeta() | Dataset version, generated-at, per-prefecture counts, spec version. Result is cached until refresh() is called on the client. | | preload({ scope }) | Warm L1 (and L2 when configured) for 'all' or a specific 1-3 digit prefix. | | isValidZipcode(zip) | Pure syntax check (^\d{7}$) — no network. | | configure(options) | Replace the singleton with a new JpzipClient configured with options. |

JpzipClient (advanced)

Construct an instance for L2 caching, custom fetch, alternate base URL, or multiple isolated caches:

import { JpzipClient } from '@jpzip/jpzip';

const client = new JpzipClient({
  baseUrl: 'https://jpzip.nadai.dev',
  fetch: globalThis.fetch,            // optional override
  memoryCacheSize: 200,               // L1 capacity in prefix buckets, default 100
  cache: myPersistentCache,           // optional L2
  onSpecMismatch: ({ expected, received }) => {
    console.warn(`jpzip spec mismatch: SDK=${expected} server=${received}`);
  },
});

JpzipClient exposes lookup / lookupGroup / lookupAll / getMeta / preload plus:

| Method | Description | |---|---| | client.refresh() | Wipe L1 (and L2 when configured) and forget the cached meta. |

When getMeta() observes that /meta.json's version has changed since the last successful fetch, L1 and L2 are cleared automatically — call getMeta() periodically to pick up dataset rollovers.

Errors

  • lookup() returns null rather than throwing for both "not found" and malformed input.
  • lookupGroup(prefix) throws Error if prefix doesn't match /^\d{1,3}$/.
  • Transient network failures and 5xx responses are retried up to 3 attempts (initial + 2 retries) with exponential backoff sleeps of 400ms and 800ms. 4xx responses other than 404 throw immediately; 404 yields null. AbortError propagates without retry.

PersistentCache interface

Bring your own L2 backend (file system, IndexedDB, Redis, Cloudflare KV, etc.):

export interface PersistentCache {
  get(key: string): Promise<Uint8Array | null>;
  set(key: string, value: Uint8Array): Promise<void>;
  delete(key: string): Promise<void>;
  clear(): Promise<void>;
}

Keys are the full bucket URLs (e.g. https://jpzip.nadai.dev/p/231.json); values are raw JSON bytes.

Exported types

ZipcodeEntry, Town, Meta, ZipcodeDict, Endpoints, JpzipClientOptions, PersistentCache — all importable from @jpzip/jpzip.

Why jpzip-js?

| | jpzip-js | jpostcode | jposta | @ken-all/kenall | |---|---|---|---|---| | Romaji (Yokohama Shi) | ✅ | ❌ | ❌ | ⚠️ Optional field | | Government codes (JIS / 総務省) | ✅ | ❌ | ⚠️ prefNum only | ✅ | | Kana | ✅ | ✅ | ❌ | ✅ | | No bundled multi-MB data in your build | ✅ CDN fetch | ⚠️ npm version embeds data | ❌ Embedded JSON | ✅ | | Monthly updates without re-npm install | ✅ Auto via CDN | ❌ Republished monthly | ❌ Manual | ✅ | | No API key | ✅ | ✅ | ✅ | ❌ Required | | Rate-limit-free | ✅ | ✅ | ✅ | ⚠️ Plan-based quota | | L1 + pluggable L2 cache | ✅ | ❌ | ❌ | ❌ | | Zero runtime deps | ✅ | ✅ | ✅ | ❌ (zod) | | ESM + CJS + TypeScript types | ✅ | ✅ | ✅ | ✅ |

Other Languages

Same API surface across all SDKs:

Go · Python · Rust · Ruby · PHP · Swift · Dart

Resources

  • Website — https://jpzip.nadai.dev
  • Protocol specjpzip/spec
  • Data ETLjpzip/data
  • MCP serverjpzip/mcp — use jpzip from Claude / ChatGPT / Cursor

Keywords

japanese postal code, japan zipcode, 郵便番号, KEN_ALL, KEN_ALL_ROME, address validation, postal code lookup typescript, typescript japanese address, node.js zipcode, hono postal code, express zipcode, cloudflare workers japan address, JIS X 0401, 総務省地方公共団体コード

License

MIT