Skip to content

Repository files navigation

@unirate/koa

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.

Features

  • unirate() middleware — attaches a typed UniRateClient to ctx.state.unirate
  • unirateRouter() — returns a @koa/router with /rate, /convert, /currencies, /vat routes
  • 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_KEY environment variable
  • Zero runtime dependencies (uses native fetch); koa and @koa/router are peer dependencies
  • TypeScript throughout, ships ESM + CommonJS + type declarations

Install

npm install @unirate/koa koa @koa/router

koa (>=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).

Quick start

Middleware

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.

Router

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

Options

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.

Routes

GET /rate

Param Required Default Description
from no USD Source currency (3-letter ISO code)
to no Target currency. Omit to return all rates for from
// /rate?from=USD&to=EUR  →
{ "rate": 0.92 }
// /rate?from=USD  →
{ "rates": { "EUR": 0.92, "GBP": 0.79 } }

GET /convert

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 }

GET /currencies

{ "currencies": ["USD", "EUR", "GBP", "..."] }

GET /vat

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 } }

Using the client directly

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
  }
}

Error handling

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.

Free vs Pro tier

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.

Example

A runnable Koa app lives in examples/server.ts:

UNIRATE_API_KEY=your-key npx tsx examples/server.ts

Related packages

UniRate 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

License

MIT © Unirate Team

About

Koa middleware and router for the UniRate currency-exchange API — typed client on ctx.state.unirate + /rate /convert /currencies /vat JSON routes. Zero runtime deps.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages