Skip to content

Thiya11/nihonpost

Repository files navigation

🗾 nihonpost

Japanese postal code → address lookup for Vue 3. TypeScript-first · works out of the box · fully self-hostable (offline, zero third-party calls) · self-updating data · handles 〒 and full-width input

〒150-0002  →  { prefecture: "東京都", city: "渋谷区", town: "渋谷" }

Why nihonpost?

Every Japanese web form needs 郵便番号 → 住所 autofill. Your options today:

API-based (zipcloud etc.) yubinbango-core2 nihonpost
Works offline / no third-party dependency ✅ when self-hosted
Zero-config first run (CDN fallback)
TypeScript types
Vue 3 composable
Promise-based ❌ (callbacks)
Full-width & 〒 input normalization partial
Full-width kana output (シブヤ not シブヤ)
Data auto-updated monthly via CI

Install

npm i nihonpost

Quick start (Vue 3)

<script setup lang="ts">
import { usePostalCode } from 'nihonpost/vue'

const { code, address, loading, notFound } = usePostalCode()
</script>

<template>
  <label>郵便番号</label>
  <input v-model="code" placeholder="150-0002" />

  <span v-if="loading">検索中…</span>
  <span v-if="notFound">該当する住所が見つかりません</span>

  <template v-if="address">
    <input :value="address.prefecture" readonly />
    <input :value="address.city" readonly />
    <input :value="address.town" readonly />
  </template>
</template>

That's it. The composable watches code, normalizes whatever the user types (〒150ー0002 works), and fills address the moment 7 digits exist.

Composable API

const {
  code,        // Ref<string>            — bind to your input
  normalized,  // ComputedRef<string|null> — "1500002" when valid
  address,     // ComputedRef<JpAddress|null> — first match
  addresses,   // ShallowRef<JpAddress[]> — all matches (some codes span 2 cities!)
  loading,     // Ref<boolean>
  notFound,    // Ref<boolean>           — valid code, no match
  error,       // Ref<Error|null>
  search,      // (value?) => Promise<JpAddress|null> — manual trigger
  reset,       // () => void
} = usePostalCode({
  auto: true,      // lookup automatically at 7 digits (default)
  debounce: 0,     // ms; rarely needed since length is fixed
})

Framework-agnostic core

No Vue? Use the core directly:

import { lookup, lookupAll, formatPostalCode } from 'nihonpost'

const addr = await lookup('150-0002')
// { prefecture: '東京都', city: '渋谷区', town: '渋谷',
//   prefectureKana: 'トウキョウト', cityKana: 'シブヤク', townKana: 'シブヤ',
//   prefectureCode: 13 }

await lookupAll('4980000')
// → 2 results: 愛知県弥富市 AND 三重県桑名郡木曽岬町 (shared codes are real!)

formatPostalCode('1500002') // "150-0002"

Data loading

The dataset (~124k codes) is chunked by the first 3 digits into ~900 small JSON files — your app loads only the chunks it touches, a few KB each.

Zero config: it works out of the box. With no setup, lookups fetch chunks from the jsDelivr CDN, pinned to your installed nihonpost version (a one-time console.info reminds you this is happening). Upgrading the package automatically moves the pin — nothing goes stale.

// Equivalent explicit form, if you prefer no console notice:
import { configureLoader, cdnLoader } from 'nihonpost'
configureLoader(cdnLoader())

Self-hosting (offline, intranet, CSP, privacy policies): copy node_modules/nihonpost/data into your static assets and point the loader at it once, at app startup:

import { configureLoader, fetchLoader } from 'nihonpost'

configureLoader(fetchLoader('/nihonpost-data'))
// lookups now GET /nihonpost-data/150.json etc., cached after first hit

Vite example — add to vite.config.ts:

import { viteStaticCopy } from 'vite-plugin-static-copy'

plugins: [
  viteStaticCopy({
    targets: [{ src: 'node_modules/nihonpost/data/*', dest: 'nihonpost-data' }],
  }),
]

Any static host works too (GitHub Pages, Cloudflare Pages, S3). It's static files — there is no API server anywhere.

Bundle size note: none of this data ever enters your JS bundle. The browser fetches only the chunks a lookup touches — a few KB each, cached after the first hit. The full ~11 MB exists only in node_modules (and travels as a 1.9 MB tarball).

Custom sources implement one function:

configureLoader(async (prefix) => {
  const res = await fetch(`https://cdn.example.com/jp-postal/${prefix}.json`)
  return res.ok ? res.json() : null
})

Rebuilding the data yourself

npm run build:data              # downloads latest utf_ken_all from Japan Post
npm run build:data -- ./my.csv  # or use a local copy

The pipeline handles KEN_ALL's sharp edges: multi-row 町域 continuation merging, parenthetical annotation stripping, 「以下に掲載がない場合」 placeholders, half-width → full-width kana, and deduplication.

A GitHub Action rebuilds the data on the 1st of every month and publishes a patch release automatically — installs stay current without a server.

Notes & limits

  • Postal codes resolve to town (町域) level — that's how Japan's postal system works. Users still type the block/building portion themselves.
  • Some codes map to multiple municipalities; address gives the first, addresses gives all. Offer a picker if you need precision.
  • Data source: Japan Post 郵便番号データ (public data).

License

MIT © Thiyagu Arunachalam

About

TypeScript-first Japanese postal code (郵便番号) lookup with address autofill and a Vue 3 composable. Zero dependencies, fully typed.

Topics

Resources

License

Stars

2 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors