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

soft_currency

v1.0.1

Published

Convert a base currency to one or many target currencies using exchangerate-api.com, with built-in rate caching.

Readme

soft_currency

npm version license node

A lightweight, zero-dependency Node.js library for converting a base currency into one or many target currencies, powered by exchangerate-api.com. Rates are cached in memory so repeated conversions don't burn through your API quota.

Table of contents

Features

  • 🪶 Zero dependencies — uses Node's built-in fetch, nothing else
  • 💱 Multi-currency conversion — convert one amount into many currencies with a single API request
  • Built-in caching — rates are cached per base currency (1 hour by default, fully configurable) to minimize API calls
  • 🛡️ Robust error handling — every failure throws a typed SoftCurrencyError with a machine-readable code
  • Input validation — invalid currency codes and amounts are rejected before any network request is made
  • 📘 TypeScript ready — type definitions ship with the package
  • 🔤 Forgiving input — currency codes are case-insensitive ('usd' works the same as 'USD')

Requirements

| Requirement | Version | | --- | --- | | Node.js | 18 or newer (for built-in fetch) | | exchangerate-api.com account | Free tier works (1,500 requests/month) |

Getting an API key

  1. Sign up for a free account at exchangerate-api.com
  2. Confirm your email address (unconfirmed accounts get an inactive-account error)
  3. Copy your API key from the dashboard

Tip: Don't hardcode the key in your source. Load it from an environment variable instead: process.env.EXCHANGE_RATE_API_KEY.

Installation

npm install soft_currency

Quick start

const { SoftCurrency } = require('soft_currency');

const sc = new SoftCurrency(process.env.EXCHANGE_RATE_API_KEY);

// 100 USD into EUR, GBP, and JPY — one API call
const result = await sc.convertMany(100, 'USD', ['EUR', 'GBP', 'JPY']);
console.log(result);
// { EUR: 92.3, GBP: 79.1, JPY: 15012 }

Usage examples

Convert to a single currency

const eur = await sc.convert(100, 'USD', 'EUR');
console.log(`$100 is €${eur.toFixed(2)}`);

Convert to multiple currencies at once

convertMany is the most efficient way to get several conversions: all target currencies share a single API request (and a single cache entry).

const prices = await sc.convertMany(49.99, 'USD', ['EUR', 'GBP', 'AUD', 'LKR']);
// { EUR: 46.14, GBP: 39.54, AUD: 76.23, LKR: 14987.05 }

for (const [currency, amount] of Object.entries(prices)) {
  console.log(`${currency}: ${amount.toFixed(2)}`);
}

Get raw exchange rates

// A single rate
const rate = await sc.getRate('USD', 'EUR');
console.log(rate); // 0.923

// Every rate for a base currency
const rates = await sc.getRates('USD');
console.log(rates.JPY); // 150.12

List supported currencies

const codes = await sc.supportedCurrencies();
console.log(codes.length);          // 160+
console.log(codes.includes('LKR')); // true

Controlling the cache

// Cache rates for 10 minutes instead of the default 1 hour
const sc = new SoftCurrency(apiKey, { cacheTtlMs: 10 * 60 * 1000 });

// Disable caching entirely — every call hits the API
const fresh = new SoftCurrency(apiKey, { cacheTtlMs: 0 });

// Manually drop all cached rates
sc.clearCache();

Using with ES modules

The package is CommonJS, but Node supports named imports from it directly:

import { SoftCurrency, SoftCurrencyError } from 'soft_currency';

Using with TypeScript

Type definitions are bundled — no @types package needed.

import { SoftCurrency, SoftCurrencyError, SoftCurrencyOptions } from 'soft_currency';

const options: SoftCurrencyOptions = { cacheTtlMs: 600_000 };
const sc = new SoftCurrency(process.env.EXCHANGE_RATE_API_KEY!, options);

const result: Record<string, number> = await sc.convertMany(100, 'USD', ['EUR', 'GBP']);

API reference

new SoftCurrency(apiKey, options?)

Creates a converter instance.

| Parameter | Type | Required | Description | | --- | --- | --- | --- | | apiKey | string | Yes | Your exchangerate-api.com API key | | options.cacheTtlMs | number | No | Cache lifetime in milliseconds. Default 3600000 (1 hour). 0 disables caching. |

Throws SoftCurrencyError (invalid-argument) if the API key is missing or empty.


convert(amount, baseCurrency, targetCurrency)

Converts an amount from one currency to another.

| Parameter | Type | Description | | --- | --- | --- | | amount | number | The amount to convert (must be finite) | | baseCurrency | string | 3-letter ISO 4217 code, e.g. 'USD' | | targetCurrency | string | 3-letter ISO 4217 code, e.g. 'EUR' |

Returns: Promise<number> — the converted amount.


convertMany(amount, baseCurrency, targetCurrencies)

Converts an amount into multiple currencies using a single API request.

| Parameter | Type | Description | | --- | --- | --- | | amount | number | The amount to convert | | baseCurrency | string | 3-letter ISO 4217 code | | targetCurrencies | string[] | Non-empty array of target codes |

Returns: Promise<Record<string, number>> — a map of currency code to converted amount, e.g. { EUR: 92.3, GBP: 79.1 }.


getRate(baseCurrency, targetCurrency)

Returns: Promise<number> — the raw conversion rate (how much 1 unit of the base currency is worth in the target currency).


getRates(baseCurrency)

Returns: Promise<Record<string, number>> — every conversion rate for the base currency. This is the method that actually talks to the API; all other methods build on it and share its cache.


supportedCurrencies(baseCurrency?)

Returns: Promise<string[]> — all supported ISO 4217 currency codes. baseCurrency defaults to 'USD'.


clearCache()

Synchronously drops all cached rates. The next call for any base currency will fetch fresh data.

How caching works

  • Rates are cached per base currency. Converting USD → EUR and then USD → JPY uses one API request; converting EUR → USD afterward makes a second request (different base).
  • A cache entry is considered fresh for cacheTtlMs milliseconds (default 1 hour). After that, the next call transparently refetches.
  • The cache lives in memory on the instance. Creating a new SoftCurrency starts with an empty cache, and nothing is persisted to disk.
  • The free exchangerate-api.com tier only refreshes rates once every 24 hours, so the default 1-hour TTL already gives you more freshness than the data source provides — there is rarely a reason to lower it.

Error handling

Every failure throws a SoftCurrencyError — a subclass of Error with a machine-readable code property, so you can branch on the cause:

const { SoftCurrency, SoftCurrencyError } = require('soft_currency');

try {
  const result = await sc.convertMany(100, 'USD', ['EUR', 'GBP']);
} catch (err) {
  if (err instanceof SoftCurrencyError) {
    switch (err.code) {
      case 'quota-reached':
        // back off, or upgrade your plan
        break;
      case 'network-error':
        // retry with backoff
        break;
      default:
        console.error(`Conversion failed [${err.code}]: ${err.message}`);
    }
  } else {
    throw err;
  }
}

| code | Thrown when | Network request made? | | --- | --- | --- | | invalid-argument | Missing API key, malformed currency code, non-numeric amount, empty target array | No | | invalid-key | The API key is not valid | Yes | | inactive-account | Account email address not confirmed | Yes | | quota-reached | Monthly API request quota exhausted | Yes | | unsupported-code | Currency code not supported by the API | Sometimes¹ | | malformed-request | The API rejected the request format | Yes | | network-error | The API could not be reached (DNS, timeout, offline) | Attempted | | http-<status> | Unexpected HTTP response without an API error type | Yes |

¹ Thrown by the API for an unknown base currency, or locally when a target currency is missing from the returned rates.

Best practices

  • Reuse one instance. The cache lives on the instance, so create a single SoftCurrency and share it across your app rather than constructing one per conversion.
  • Prefer convertMany when you need several currencies — it's one request instead of N.
  • Keep your API key out of source control. Use environment variables or a secrets manager.
  • Handle quota-reached. On the free tier (1,500 requests/month) a busy app can hit the quota; the default caching makes this unlikely, but your error handling should still account for it.

License

ISC © Soft Detroits