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

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, 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 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:
{
  "title": "Europe 2025",
  "currency": "USD",
  "start_date": "2025-06-01",
  "end_date": "2025-06-14"
}
  1. Verify UI Display: The Budget UI reads this field from trip.schema.ts and displays all cost entries in the selected currency.

  2. 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:

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

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:

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 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 using ISO-4217 codes.
  • The ExchangeRateService in 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 (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). 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →