# Add currency exchange rates and conversion to TypeScript

Use typed responses with fetch to read rates and convert currencies. Keep the key on the server and cache the latest response until nextUpdateAt.

## Get a free API key

Sign up at https://currencyexchangerate-api.com/auth/login, then create a key under API keys in the console. The free plan includes 3,000 requests per month with no credit card. Store the secret as CXR_API_KEY in server-side environment configuration; never expose it in browser code.

File: `.env`

```shell
CXR_API_KEY=cxr_your_secret_key
```

## Create a reusable client and cache rates

Use the standard HTTP client. Cache every /latest response until nextUpdateAt: rates change at most once by then, and reusing the response saves quota. The helper also exposes /convert.

File: `rates.ts`

```typescript
const API_BASE_URL = 'https://api.currencyexchangerate-api.com/api/v1';
const key = process.env.CXR_API_KEY!;

type Latest = {
  base: string;
  updatedAt: string;
  nextUpdateAt: string;
  rates: Record<string, number>;
};

type Conversion = {
  from: string;
  to: string;
  amount: number;
  rate: number;
  result: number;
  updatedAt: string;
};

const cache = new Map<string, { data: Latest; until: number }>();

async function request<T>(path: string): Promise<T> {
  const response = await fetch(API_BASE_URL + path, {
    headers: { Authorization: 'Bearer ' + key },
  });
  if (!response.ok) {
    throw new Error('Rates API status ' + response.status);
  }
  return response.json() as Promise<T>;
}

export async function latest(
  base = 'USD',
  symbols = 'EUR,GBP',
): Promise<Latest> {
  const id = base + ':' + symbols;
  const hit = cache.get(id);
  if (hit && Date.now() < hit.until) {
    return hit.data;
  }
  const query = new URLSearchParams({ base, symbols });
  const data = await request<Latest>('/latest?' + query);
  cache.set(id, { data, until: Date.parse(data.nextUpdateAt) });
  return data;
}

export function convert(
  from: string,
  to: string,
  amount: number,
): Promise<Conversion> {
  const query = new URLSearchParams({ from, to, amount: String(amount) });
  return request<Conversion>('/convert?' + query);
}
```

## Use it in TypeScript

Call the helper from a server route, controller, view, command, or script. Format converted values for the user’s locale.

File: `route.ts`

```typescript
import { convert } from './rates';

const data = await convert('EUR', 'GBP', 100);
const formatted = new Intl.NumberFormat('en-GB', {
  style: 'currency',
  currency: 'GBP',
}).format(data.result);
```

## Handle API errors

A 401 means the key is invalid. A 429 can mean a rate limit or quota limit; read Retry-After in seconds before retrying. Other errors use application/problem+json.

File: `errors.js`

```javascript
// In get(), replace the generic throw with:
//   if (!response.ok) throw await apiError(response);
async function apiError(response) {
  const problem = await response.json().catch(() => ({}));
  if (response.status === 401) {
    return new Error('Check CXR_API_KEY');
  }
  if (response.status === 429) {
    const retryAfter = response.headers.get('Retry-After');
    return new Error('Retry after ' + retryAfter + ' seconds');
  }
  return new Error(problem.detail || 'Rates API status ' + response.status);
}
```

## Format the amount for display

Use locale-aware currency formatting instead of adding a symbol manually.

File: `format.js`

```javascript
const formatter = new Intl.NumberFormat('en-GB', {
  style: 'currency',
  currency: 'EUR',
});
const formatted = formatter.format(85.249105);
```

## Related links

- [API documentation](https://currencyexchangerate-api.com/docs.md)
- [All integration guides](https://currencyexchangerate-api.com/guides.md)
- [Get a free API key](https://currencyexchangerate-api.com/auth/login)
