Compare

@koshmoney/countries vs iso-3166-1 + iso-3166-2

Before unified country data libraries existed, the standard approach was to install separate packages for ISO 3166-1 (countries) and ISO 3166-2 (subdivisions). This works but comes with trade-offs: inconsistent APIs, manual integration, and duplicate country data.

This page compares the separate-package approach with @koshmoney/countries and provides migration examples.

The Separate Package Approach

A typical setup using separate packages looks like this:

// Install two (or more) packages
npm install iso-3166-1 iso-3166-2
 
// Two different APIs
import iso31661 from 'iso-3166-1';
import iso31662 from 'iso-3166-2';
 
// Country lookup
const us = iso31661.whereAlpha2('US');
// { country: 'United States of America', alpha2: 'US', alpha3: 'USA', numeric: '840' }
 
// Subdivision lookup
const ca = iso31662.subdivision('US-CA');
// { name: 'California', type: 'state', parent: null, countryCode: 'US', ... }

Feature Comparison

Feature@koshmoney/countriesiso-3166-1 + iso-3166-2
Countries249 (ISO 3166-1)249 (ISO 3166-1)
Subdivisions5,000+5,000+
Single PackageYesNo (2+ packages)
Unified APIYesNo (different APIs)
Postal Code Validation150+ countriesNot included
Currency DataISO 4217Not included
Phone Dial CodesVia libphonenumber-jsNot included
EU/SEPA/EEA MembershipBuilt-inNot included
Geography (Continent/Region)UN M49Not included
Code ValidationFull suiteBasic
TypeScript SupportBuilt-inVaries
Tree-ShakingPer-country subdivision importsNo
Dependencies0 (core)0 each

Problems with Separate Packages

1. Inconsistent APIs

Each package has its own naming conventions, return types, and error handling:

// iso-3166-1: returns array
iso31661.whereAlpha2('US');
 
// iso-3166-2: returns object or undefined
iso31662.subdivision('US-CA');
 
// Different property names for the same concept
// iso-3166-1: { country: 'United States of America' }
// iso-3166-2: { name: 'California' }

With @koshmoney/countries, the API is consistent:

import { country, subdivision } from '@koshmoney/countries';
 
// Same pattern for both
country.whereAlpha2('US');
// { name: 'United States', alpha2: 'US', alpha3: 'USA', numeric: '840' }
 
subdivision.whereCode('US-CA');
// { code: 'US-CA', name: 'California', type: 'State', countryCode: 'US', ... }

2. No Cross-Module Features

With separate packages, you cannot easily get a country with its subdivisions, or validate a subdivision against its parent country:

import { country, subdivision } from '@koshmoney/countries';
 
// Get a country with all its subdivisions in one call
country.withSubdivisions('US');
// { name: 'United States', alpha2: 'US', ..., subdivisions: [...] }
 
// Validate that a subdivision belongs to a country
subdivision.isValidRegion('US', 'CA');  // true
subdivision.isValidRegion('US', 'ON');  // false (ON is Canadian)

3. No Additional Data Modules

Separate ISO packages only cover country and subdivision codes. You need even more packages for currencies, dial codes, and membership data:

# The separate-package approach
npm install iso-3166-1 iso-3166-2 iso-4217 libphonenumber-js
# Plus manual EU/SEPA membership lists
 
# The unified approach
npm install @koshmoney/countries

4. Version Coordination

When ISO 3166 is updated, separate packages may update at different times, leading to inconsistencies between your country and subdivision data.

Migration Guide

Country Lookups

// Before (iso-3166-1)
import iso31661 from 'iso-3166-1';
 
iso31661.whereAlpha2('US');
// { country: 'United States of America', alpha2: 'US', alpha3: 'USA', numeric: '840' }
 
iso31661.whereAlpha3('GBR');
iso31661.whereNumeric('276');
// After (@koshmoney/countries)
import { country } from '@koshmoney/countries';
 
country.whereAlpha2('US');
// { name: 'United States', alpha2: 'US', alpha3: 'USA', numeric: '840' }
 
country.whereAlpha3('GBR');
country.whereNumeric(276);  // Note: accepts number, not string

Key difference: The property country is renamed to name for clarity.

Subdivision Lookups

// Before (iso-3166-2)
import iso31662 from 'iso-3166-2';
 
iso31662.subdivision('US-CA');
// { name: 'California', type: 'state', parent: null, countryCode: 'US', regionCode: 'CA' }
 
iso31662.country('US');
// Returns subdivision data grouped by type
// After (@koshmoney/countries)
import { subdivision } from '@koshmoney/countries';
 
subdivision.whereCode('US-CA');
// { code: 'US-CA', name: 'California', type: 'State', countryCode: 'US', regionCode: 'CA' }
 
subdivision.forCountry('US');
// Returns flat array of all US subdivisions

Key differences:

  • subdivision() becomes whereCode()
  • country() becomes forCountry()
  • Type values are capitalized ('State' not 'state')

Code Conversion

// Before
import iso31661 from 'iso-3166-1';
// Manual conversion through lookup
const result = iso31661.whereAlpha2('US');
const alpha3 = result?.alpha3; // 'USA'
// After
import { country } from '@koshmoney/countries';
// Direct conversion functions
country.alpha2ToAlpha3('US');    // 'USA'
country.alpha3ToAlpha2('GBR');   // 'GB'
country.alpha2ToNumeric('DE');   // '276'
country.numericToAlpha2(840);    // 'US'

Validation

// Before: manual check
import iso31661 from 'iso-3166-1';
const isValid = iso31661.whereAlpha2('US') !== undefined;
// After: dedicated validation functions
import { country, subdivision } from '@koshmoney/countries';
 
country.isAlpha2('US');              // true
country.isAlpha3('USA');             // true
country.isValid('US');               // true (any format)
subdivision.isValidCode('US-CA');    // true
subdivision.isValidRegion('US', 'CA'); // true

Bonus Features After Migration

Once you migrate, you get access to modules that separate packages do not provide:

// Postal code validation
import { postalCode } from '@koshmoney/countries';
postalCode.isValid('US', '90210');      // true
postalCode.getName('US');               // 'ZIP Code'
 
// Currency data
import { currency } from '@koshmoney/countries/currency';
currency.getCurrency('JP');
// { code: 'JPY', symbol: '\u00a5', name: 'Japanese Yen' }
 
// EU/SEPA membership
import { membership } from '@koshmoney/countries/membership';
membership.isEU('FR');        // true
membership.isSEPA('GB');      // true
 
// Geography
import { geography } from '@koshmoney/countries/geography';
geography.getContinent('BR'); // 'South America'
geography.getRegion('JP');    // 'Eastern Asia'

When to Stay with Separate Packages

There are valid reasons to keep using separate packages:

  • You already have a large codebase that uses iso-3166-1 or iso-3166-2 extensively and migration cost is high
  • You need a specific feature of iso-3166-2 like parent subdivision relationships
  • You prefer minimal installs and only need country codes without any additional features

For new projects, @koshmoney/countries provides a better developer experience with a unified API and additional features.