vite-plugin-unirate
v0.1.0
Published
Vite plugin for UniRate — a build-time `virtual:unirate` module with baked currency rates/currencies, plus a dev-server proxy that keeps your API key server-side.
Maintainers
Readme
vite-plugin-unirate
Vite plugin for UniRate that gives you two ways to use live currency data without ever shipping your API key to the browser:
virtual:unirate— a build-time virtual module. The plugin fetches rates and the supported-currency list once during the build and bakes them into your bundle as frozen, tree-shakeable constants. No runtime fetch, no client-side key.- Dev-server proxy —
configureServermounts/__unirate/rate|convert|currencies|vat. Those routes call UniRate server-side, so the API key stays on your machine while you develop against live rates.
- Zero runtime dependencies (uses native
fetch). viteis a peer dependency (>=5.0.0).- Full typed error mapping (400/401/403/404/429/503).
Install
npm install vite-plugin-unirateYou'll need a free UniRate API key — grab one at https://unirateapi.com.
Configure
// vite.config.ts
import { defineConfig } from "vite";
import unirate from "vite-plugin-unirate";
export default defineConfig({
plugins: [
unirate({
apiKey: process.env.UNIRATE_API_KEY,
baseCurrency: "USD",
currencies: ["EUR", "GBP", "JPY", "CAD", "AUD"],
}),
],
});If apiKey is omitted the plugin reads UNIRATE_API_KEY from the environment.
Options
| Option | Type | Default | Description |
|---|---|---|---|
| apiKey | string | process.env.UNIRATE_API_KEY | UniRate API key. Used server-side only. |
| baseCurrency | string | "USD" | Base currency the build-time snapshot is keyed against. |
| currencies | string[] | all UniRate currencies | Optional allowlist to shrink the baked snapshot. |
| baseUrl | string | "https://api.unirateapi.com" | API host. Override for self-hosted or testing. |
1. Build-time virtual module
import { rates, currencies, base, fetchedAt } from "virtual:unirate";
console.log(`1 ${base} = ${rates.EUR} EUR`); // baked at build timeEverything resolves to constants in the final bundle — there is no network call at runtime and the API key never leaves the build.
Typing the virtual module
Add the ambient reference once (e.g. in your src/env.d.ts next to /// <reference types="vite/client" />):
/// <reference types="vite-plugin-unirate/virtual" />Now the import is fully typed:
import snapshot from "virtual:unirate";
// snapshot: { base: string; rates: Record<string, number>; currencies: string[]; fetchedAt: string }2. Dev-server proxy
While vite dev is running, four routes call UniRate on the server:
| Route | Upstream | Response |
|---|---|---|
| GET /__unirate/rate?from=USD&to=EUR | /api/rates | { "rates": { "USD": 1, "EUR": 0.925 } } |
| GET /__unirate/convert?from=USD&to=EUR&amount=100 | /api/convert | { "result": 92.5 } |
| GET /__unirate/currencies | /api/currencies | { "currencies": ["USD", "EUR", ...] } |
| GET /__unirate/vat?country=DE | /api/vat/rates | { "vat_data": { ... } } (omit country for all) |
const res = await fetch("/__unirate/convert?from=USD&to=EUR&amount=100");
const { result } = await res.json();Because the key lives in the Vite process, nothing sensitive ships to the browser. For production, either rely on the build-time virtual:unirate snapshot or run the same proxy behind your own backend.
Error handling
Upstream UniRate errors are mapped to typed errors and re-surfaced with the correct status. On the dev-server proxy the HTTP status is preserved and the body is { "error": "<message>" }.
| Status | Error type | Meaning |
|---|---|---|
| 400 | InvalidDateError | Invalid request parameters |
| 401 | AuthenticationError | Missing or invalid API key |
| 403 | APIError | Endpoint requires a Pro subscription |
| 404 | InvalidCurrencyError | Currency not found or no data available |
| 429 | RateLimitError | Rate limit exceeded |
| 503 | APIError | Service unavailable |
All inherit from UniRateError. Network/transport failures surface as the base UniRateError (and become a 502 on the proxy).
import { UniRateClient, AuthenticationError } from "vite-plugin-unirate";
const client = new UniRateClient({ apiKey: process.env.UNIRATE_API_KEY! });
try {
await client.getRates("USD", ["EUR"]);
} catch (err) {
if (err instanceof AuthenticationError) { /* fix the key */ }
}Rate limits
The free tier is rate-limited; a 429 maps to RateLimitError. Historical and time-series endpoints are Pro-gated (403) and are intentionally not exposed by this plugin.
Related clients
Part of the UniRate client family — see github.com/UniRate-API for the Astro, Eleventy, Next, Nuxt, SvelteKit, Vue, and framework-agnostic Node/Python/Go/Rust/etc. clients.
License
MIT © 2026 Unirate Team
