Koa middleware and router for the UniRate API — free currency exchange rates, conversion, and VAT rates.
Attach a typed UniRate client to ctx.state.unirate, or mount a ready-made set of JSON endpoints with a @koa/router, and keep your API key server-side.
unirate()middleware — attaches a typedUniRateClienttoctx.state.unirateunirateRouter()— returns a@koa/routerwith/rate,/convert,/currencies,/vatroutes- Full error mapping — UniRate statuses (400 / 401 / 403 / 404 / 429 / 503) mirrored to proper HTTP responses; transport failures become 502
- Currency-code validation + uppercasing
- API key read from options or the
UNIRATE_API_KEYenvironment variable - Zero runtime dependencies (uses native
fetch);koaand@koa/routerare peer dependencies - TypeScript throughout, ships ESM + CommonJS + type declarations
npm install @unirate/koa koa @koa/routerkoa (>=2.0.0) and @koa/router (>=12.0.0) are peer dependencies. Get a free UniRate API key at unirateapi.com. Requires Node 18+ (for the native fetch).
import Koa from "koa";
import { unirate } from "@unirate/koa";
const app = new Koa();
app.use(unirate()); // reads process.env.UNIRATE_API_KEY
app.use(async (ctx) => {
const rate = await ctx.state.unirate.getRate("USD", "EUR");
ctx.body = { pair: "USD/EUR", rate };
});
app.listen(3000);ctx.state.unirate is fully typed via Koa's DefaultState augmentation — no casting needed.
import Koa from "koa";
import { unirateRouter } from "@unirate/koa";
const app = new Koa();
const router = unirateRouter();
router.prefix("/api/unirate");
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000);That mounts:
GET /api/unirate/rate?from=USD&to=EUR
GET /api/unirate/convert?from=USD&to=EUR&amount=100
GET /api/unirate/currencies
GET /api/unirate/vat?country=DE
Both unirate() and unirateRouter() accept the same options:
| Option | Type | Default | Description |
|---|---|---|---|
apiKey |
string |
— | API key. If omitted, resolved from the env variable |
envKey |
string |
"UNIRATE_API_KEY" |
Name of the env variable to read the key from |
baseUrl |
string |
https://api.unirateapi.com |
Override the API base URL |
fetch |
typeof fetch |
globalThis.fetch |
Inject a custom fetch |
timeoutMs |
number |
30000 |
Request timeout |
app.use(unirate({ apiKey: process.env.UNIRATE_API_KEY }));
// or a custom variable name
const router = unirateRouter({ envKey: "MY_UNIRATE_KEY" });The API key is resolved eagerly when the middleware/router is built, so a missing key throws at startup rather than per-request.
| Param | Required | Default | Description |
|---|---|---|---|
from |
no | USD |
Source currency (3-letter ISO code) |
to |
no | — | Target currency. Omit to return all rates for from |
| Param | Required | Default | Description |
|---|---|---|---|
from |
no | USD |
Source currency |
to |
yes | — | Target currency |
amount |
no | 1 |
Amount to convert (positive number) |
// /convert?from=USD&to=EUR&amount=100 →
{ "result": 92.5 }{ "currencies": ["USD", "EUR", "GBP", "..."] }| Param | Required | Default | Description |
|---|---|---|---|
country |
no | — | ISO-3166 alpha-2 code. Omit for all countries |
// /vat?country=DE →
{ "country": "DE", "vat_data": { "country_code": "DE", "country_name": "Germany", "vat_rate": 19 } }The internal client is exported for direct use in your own handlers:
import { UniRateClient, RateLimitError } from "@unirate/koa";
const client = new UniRateClient({ apiKey: "..." });
try {
const rate = await client.getRate("USD", "EUR"); // number
const all = await client.getRate("USD"); // Record<string, number>
const eur = await client.convert("EUR", 100, "USD"); // number
const codes = await client.getSupportedCurrencies(); // string[]
const vat = await client.getVatRate("DE"); // { country, vat_data }
} catch (err) {
if (err instanceof RateLimitError) {
// back off and retry
}
}The router maps thrown errors to HTTP responses with a { "error": string } body:
| Status | Meaning |
|---|---|
400 |
Invalid parameter (bad currency/country code, missing/invalid amount) |
401 |
Missing or invalid API key |
403 |
Endpoint requires a Pro subscription |
404 |
Currency not found or no data available |
429 |
Rate limit exceeded |
500 |
API key not configured on the server |
502 |
UniRate upstream / transport error |
503 |
Service unavailable |
When using the client directly, these map to typed error classes — all extending UniRateError:
InvalidRequestError, AuthenticationError, ProRequiredError, InvalidCurrencyError, RateLimitError, ServiceUnavailableError.
The free tier covers /rate, /convert, /currencies, and /vat. Historical and time-series endpoints require a Pro subscription and are not exposed by this package.
A runnable Koa app lives in examples/server.ts:
UNIRATE_API_KEY=your-key npx tsx examples/server.tsUniRate API client libraries: Python · Node.js · Go · Rust · Ruby · PHP · Java · Swift · .NET
Framework integrations: Next.js · Nuxt · SvelteKit · Astro · NestJS · Remix · Hono · Angular · Vue · React
Platform: Cloudflare Workers · MCP server · CLI
MIT © Unirate Team