# Add currency exchange rates and conversion to Express.js

Add server-side Express endpoints with the standard fetch client. Cache the latest response until nextUpdateAt to conserve quota.

## 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.js`

```javascript
const API_BASE_URL = 'https://api.currencyexchangerate-api.com/api/v1';
const key = process.env.CXR_API_KEY;
const saved = new Map();

async function get(path) {
  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();
}

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

export async function convert(from, to, amount) {
  const query = new URLSearchParams({ from, to, amount: String(amount) });
  return get('/convert?' + query);
}
```

## Use it in Express.js

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

File: `app.js`

```javascript
import express from 'express';

import { latest, convert } from './rates.js';

const app = express();

app.get('/api/rates', async (_req, res) => res.json(await latest()));

app.get('/api/convert', async (req, res) => {
  const { from, to, amount } = req.query;
  const data = await convert(String(from), String(to), Number(amount));
  res.json(data);
});

app.listen(3000);
```

## 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)
