# How to Configure Multi-Currency Support and Exchange Rates in TREK Trip Budgets

> Easily configure multi-currency trip budgets in TREK using ISO-4217 codes. Learn how TREK automatically converts amounts with live exchange rates from the Frankfurter API.

- Repository: [Maurice/TREK](https://github.com/mauriceboe/TREK)
- Tags: how-to-guide
- Published: 2026-07-11

---

**TLDR:** TREK enables multi-currency trip budgets by storing an ISO-4217 currency code in the trip's `currency` field and automatically converting amounts via the `ExchangeRateService`, which retrieves live rates from the Frankfurter API and caches them for six hours.

TREK (mauriceboe/TREK) is an open-source trip planning application that handles complex budget calculations across multiple currencies. Configuring multi-currency support and exchange rates in TREK trip budgets requires setting the trip's currency code and understanding how the server converts foreign amounts using cached exchange rates. The architecture separates currency storage in the trip schema from the conversion logic handled by the `ExchangeRateService`.

## Understanding the Currency Architecture

### Trip Schema Configuration

In [`shared/src/trip/trip.schema.ts`](https://github.com/mauriceboe/TREK/blob/main/shared/src/trip/trip.schema.ts), the trip entity includes a **`currency`** field that stores the ISO-4217 code (e.g., `USD`, `EUR`, `JPY`) selected for the trip. This field determines how the Budget UI displays all cost amounts and which currency serves as the base for aggregations. When users create or edit a trip via the API, they can select from **47 supported currencies**, with the selected code persisted to the trip record.

### The ExchangeRateService

The [`server/src/services/exchangeRateService.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/services/exchangeRateService.ts) file implements the core conversion logic. The **`getRates(base)`** method fetches live exchange rates from the Frankfurter API (`https://api.frankfurter.dev/v2/rates?base={base}`), seeds the map with the base currency set to 1, and caches results for six hours. The **`convertWithRates`** helper function converts any amount from a source currency to the target currency using these cached rates, falling back to identity conversion if a rate is missing. The service also coalesces concurrent requests to prevent duplicate API calls when multiple budget calculations trigger simultaneously.

## How Exchange Rate Conversion Works

The conversion process follows a strict lifecycle to ensure performance and reliability:

1. **Cache Check**: When `getRates(base)` is called, it first checks the in-memory cache for existing rates.
2. **API Fetch**: On cache miss, the service calls an internal `fetchRates(base)` method to retrieve fresh rates from Frankfurter.
3. **Response Processing**: The API returns an array of `{quote, rate}` objects; the service inserts `{BASE: 1}` to ensure the base currency is always selectable.
4. **Caching**: Rates are stored with a **6-hour TTL** (`TTL_MS`), and subsequent calls within this window return the cached map.
5. **Graceful Degradation**: If the fetch fails (offline or API down), the service returns `null`, and the UI treats amounts as already in the base currency (identity conversion).

## Configuring Multi-Currency for a Trip

To enable multi-currency support for a specific trip:

1. **Set the Trip Currency**: Create or update the trip via the API or UI, specifying the `currency` field:

```json
{
  "title": "Europe 2025",
  "currency": "USD",
  "start_date": "2025-06-01",
  "end_date": "2025-06-14"
}

```

2. **Verify UI Display**: The Budget UI reads this field from [`trip.schema.ts`](https://github.com/mauriceboe/TREK/blob/main/trip.schema.ts) and displays all cost entries in the selected currency.

3. **Handle Foreign Costs**: When adding costs in different currencies (e.g., a JPY receipt), the backend automatically converts the amount to the trip's base currency using `convertWithRates` before storage.

## Handling Foreign Currency Costs Server-Side

When users input costs in currencies different from the trip's base currency, convert them server-side using the `ExchangeRateService`:

```typescript
import { getRates, convertWithRates } from '@/server/services/exchangeRateService';

async function addCost(tripId: number, amount: number, cur: string) {
  const trip = await getTripById(tripId);          // contains trip.currency
  const rates = await getRates(trip.currency);    // cached rates for base currency
  const amountInBase = convertWithRates(amount, cur, trip.currency, rates);
  
  await db.insert('costs', {
    trip_id: tripId,
    amount: amountInBase,
    currency: trip.currency,
    original_amount: amount,
    original_currency: cur,
  });
}

```

## Customizing the Base Currency (Advanced)

By default, the `ExchangeRateService` uses **EUR** as the base currency when no base is supplied. Advanced deployments can change this default by editing the key calculation in `getRates` (around line 38 in [`exchangeRateService.ts`](https://github.com/mauriceboe/TREK/blob/main/exchangeRateService.ts)):

```typescript
const key = (base || 'EUR').toUpperCase(); // modify 'EUR' as needed

```

Changing this value affects all subsequent rate fetches and conversions that don't explicitly specify a base currency.

## Client-Side Currency Updates

Use the client SDK to update a trip's currency dynamically:

```typescript
import { ctx } from '@/client/context';

// Update trip #42 to use Canadian Dollars
await ctx.trips.update(42, { currency: 'CAD' });

```

After updating, the Currency-Converter widget in [`client/src/hooks/useExchangeRates.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/hooks/useExchangeRates.ts) fetches fresh rates to reflect the new base currency in the dashboard.

## Summary

- TREK stores trip currencies in the **`currency`** field of [`shared/src/trip/trip.schema.ts`](https://github.com/mauriceboe/TREK/blob/main/shared/src/trip/trip.schema.ts) using ISO-4217 codes.
- The **`ExchangeRateService`** in [`server/src/services/exchangeRateService.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/services/exchangeRateService.ts) handles all conversions via the Frankfurter API.
- Rates are **cached for 6 hours** to minimize API calls and improve performance.
- The **`convertWithRates`** function provides safe fallback to identity conversion when rates are unavailable.
- Users can select from **47 supported currencies** in the Budget UI currency picker.
- The default base currency is **EUR**, configurable in the service source code.

## Frequently Asked Questions

### What happens if the Frankfurter API is offline?

If the API request fails, `getRates` returns `null` and the conversion logic falls back to identity conversion, treating the input amount as already being in the trip's base currency. This ensures the application remains functional during network outages, though conversions will be inaccurate until the API recovers.

### Can I change the default base currency from EUR?

Yes, advanced users can modify the default base currency by editing the `key` calculation in [`server/src/services/exchangeRateService.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/services/exchangeRateService.ts) (line 38), changing the hardcoded `'EUR'` string to their preferred ISO-4217 code. This affects all trips that don't explicitly specify a base currency in their configuration.

### How long are exchange rates cached in TREK?

Exchange rates are cached for **six hours** (`TTL_MS` constant in [`exchangeRateService.ts`](https://github.com/mauriceboe/TREK/blob/main/exchangeRateService.ts)). Subsequent requests within this window return the cached values, significantly reducing API load and improving response times for budget calculations.

### Does TREK support real-time exchange rate updates?

While the dashboard Currency-Converter widget fetches fresh rates when users change source or target currencies, the backend service caches rates for six hours. For true real-time updates, you would need to modify the `TTL_MS` constant or implement a WebSocket connection to the Frankfurter API.