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.
Maintainers
Readme
soft_currency
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
- Requirements
- Getting an API key
- Installation
- Quick start
- Usage examples
- API reference
- How caching works
- Error handling
- Best practices
- License
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
SoftCurrencyErrorwith a machine-readablecode - ✅ 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
- Sign up for a free account at exchangerate-api.com
- Confirm your email address (unconfirmed accounts get an
inactive-accounterror) - 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_currencyQuick 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.12List supported currencies
const codes = await sc.supportedCurrencies();
console.log(codes.length); // 160+
console.log(codes.includes('LKR')); // trueControlling 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 → EURand thenUSD → JPYuses one API request; convertingEUR → USDafterward makes a second request (different base). - A cache entry is considered fresh for
cacheTtlMsmilliseconds (default 1 hour). After that, the next call transparently refetches. - The cache lives in memory on the instance. Creating a new
SoftCurrencystarts 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
SoftCurrencyand share it across your app rather than constructing one per conversion. - Prefer
convertManywhen 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
