LearnTutorial

Currency conversion in JavaScript and Node.js

Fetch an exchange rate and convert an amount in JavaScript with the built-in fetch API. The same pattern works in Node 18+, Deno, Bun, and modern browsers.

1 Oct 2026 · 5 min read

Start with a GET to /v1/latest/USD using fetch. The response is plain JSON, with per-currency sources and effective_at fields so your app can show what it received and when. Your first call can be anonymous.

  • Global fetch works out of the box in Node 18+, Deno, Bun, and browsers with no extra dependencies.
  • One GET to /v1/latest/{base} returns rates for 144 currencies, plus source and market_session.
  • Pass your key as Authorization: Bearer exr_live_..., or use X-API-Key if your platform cannot set Authorization.
  • The free tier is 5,000 calls a month at 10 requests a minute, with no card required.
  • Rates are indicative, for reference and analytics only, not a dealing quote.

Your first call, no key needed

In Node 18 or any modern browser you already have fetch. Check the HTTP status before reading the EUR rate:

javascript · no key
const response = await fetch("https://api.exchangerate.dev/v1/latest/USD?symbols=EUR,GBP", {  signal: AbortSignal.timeout(10_000),});if (!response.ok) throw new Error(`HTTP ${response.status}; Retry-After: ${response.headers.get("Retry-After")}`);const data = await response.json();console.log(data.rates.EUR);console.log(data.sources.EUR, data.effective_at.EUR);console.log(data.market_session);

Anonymous calls are capped per IP address, so move to a free key before you go past a quick test.

Add a key and error handling

Pass your key as a bearer token in the Authorization header. Check res.ok and throw on a bad status so errors surface early:

javascript · authenticated call with error handling
// Node.js server only. Never put an account key in browser code.const KEY = process.env.EXCHANGERATE_API_KEY;if (!KEY) throw new Error("Set EXCHANGERATE_API_KEY on your server");async function getLatest(base = "USD") {  const response = await fetch(`https://api.exchangerate.dev/v1/latest/${encodeURIComponent(base)}`, {    headers: { Authorization: `Bearer ${KEY}` },    redirect: "error",    signal: AbortSignal.timeout(10_000),  });  if (!response.ok) throw new Error(`HTTP ${response.status}; Retry-After: ${response.headers.get("Retry-After")}`);  return response.json();}const data = await getLatest("USD");console.log(data.rates.GBP, data.effective_at.GBP);

The X-API-Key: exr_live_... header is also accepted, for platforms that cannot set an Authorization header (some serverless runtimes, Zapier, and similar no-code tools).

Convert an amount

For a direct conversion call /v1/convert/{from}/{to}/{amount}. The response gives you both the rate and the converted amount in one round-trip:

javascript · convert 100 USD to EUR
// Node.js server only.const KEY = process.env.EXCHANGERATE_API_KEY;if (!KEY) throw new Error("Set EXCHANGERATE_API_KEY on your server");const response = await fetch("https://api.exchangerate.dev/v1/convert/USD/EUR/100", {  headers: { Authorization: `Bearer ${KEY}` },  redirect: "error",  signal: AbortSignal.timeout(10_000),});if (!response.ok) throw new Error(`HTTP ${response.status}; Retry-After: ${response.headers.get("Retry-After")}`);const data = await response.json();console.log(data.rate, data.converted);

The convert endpoint returns the same source and market_session fields as the latest endpoint, so you can show users the rate class and whether the interbank trading week is open.

Use currency.js for amount arithmetic

currency.js handles amounts and formatting. It does not fetch exchange rates. Get the rate from the API first, keep that rate at full precision, and use the target currency’s number of decimal places when calculating the displayed amount.

JavaScript · currency.js
import currency from "currency.js";const response = await fetch("https://api.exchangerate.dev/v1/latest/USD?symbols=EUR", {  signal: AbortSignal.timeout(10_000),});if (!response.ok) throw new Error(`HTTP ${response.status}`);const data = await response.json();const rate = data.rates.EUR;if (!Number.isFinite(rate) || rate <= 0) throw new Error("Invalid rate");const euros = currency("100.00", { precision: 2, symbol: "€" }).multiply(rate);console.log(euros.format());

Install the package with npm install currency.js. Its default precision is two decimal places; set precision: 0 for a yen amount. This example is for display estimates. Keep your original amount, full rate, and observation time for an audit trail.

Understand source and market_session

Use effective_at[currency] for the observation time of a displayed currency, and sources[currency] for its source. source summarizes the whole response; market_session describes the trading session rather than the age of a rate. source identifies the data class: live is live market data updated about every 60 seconds on trading days, ecb_daily is the ECB daily fix, and market_daily is the stored daily market-feed close for currencies without an ECB fix. market_session is open, weekend, or interbank_closed for a known non-weekend closure.

Field valueWhat it meansWhen it moves
source: liveIndicative spot rateIntraday (~60s), trading week
source: ecb_dailyEuropean Central Bank reference fixOnce per business day (~16:00 CET)
source: market_dailyStored close, no ECB fixDaily market feed
market_session: weekendSaturday or Sunday (interbank closed)Reference fixes carry Friday's value; live feed not updating

The data_updated_at field is the oldest contributing observation; it is not necessarily the age of the currency you display. timestamp records when the response was built. Together they let you decide how to cache and display the value.

Indicative rates only

These rates are published for reference, analytics, and display. They are not a dealing quote and must not be used to settle a trade or transfer. Every response states this in its notice field.

Free tier limits

A free key gives you 5,000 calls per month at 10 requests per minute. No card is required to sign up. If you exceed the per-minute limit the API returns a 429; back off and retry. The monthly quota resets at 00:00 UTC on the first day of each calendar month. Honor Retry-After on 429 responses.

  • 5,000 calls per month
  • 10 requests per minute
  • No credit card required
  • Covers /v1/latest, /v1/convert, /v1/range, and /v1/{date}/{base}

Questions

Do I need a bundler or extra packages to use this API in JavaScript?

No. The global fetch function is built into Node 18+, Deno, Bun, and all modern browsers. You can make your first call with a single await fetch(...) and res.json(). No third-party HTTP library is required.

How do I pass my API key in a fetch request?

Send it in the Authorization header as a bearer token: { "Authorization": "Bearer exr_live_..." }. The X-API-Key header is also accepted as an alias, which is useful on platforms that cannot set a custom Authorization header.

What is the difference between /v1/latest and /v1/convert?

The latest endpoint returns rates for all 144 supported currencies against a chosen base. The convert endpoint takes a from currency, a to currency, and an optional amount, and returns the rate plus the converted value in one call. Use convert when you only need one pair and want the result calculated for you.

Can I use these rates to process a payment or settle a transaction?

No. The rates are indicative, published for reference, display, and analytics only. They are not dealing quotes. Every response includes a notice field stating this. For financial settlements, consult your payment provider for the rate they apply.

Keep reading

Start with a free API key

5,000 requests a month on the free plan. No credit card.

Get a free key