Node.js has become the default runtime for building production backends, APIs, and real-time applications. When you need to handle currency conversion, exchange rates, or multi-currency transactions in Node.js, integrating a currency API correctly is essential for performance, reliability, and cost efficiency. This guide covers everything from basic setup to production-ready patterns including caching strategies, error handling, and retry logic.
Why Node.js + Currency API?
Node.js excels at I/O-bound operations like API requests. A single Node.js instance can handle thousands of concurrent currency conversion requests. Combined with the right currency API (like Finexly), you can build scalable platforms that serve global users in multiple currencies. For browser-based integration, see our JavaScript guide. For Python backends, see the Python tutorial.
Setting Up Your Node.js Project
Start by initializing a new Node.js project and installing dependencies:
mkdir currency-api-app
cd currency-api-app
npm init -y
npm install axios dotenv expressCreate a .env file to store your API key securely:
FINEXLY_API_KEY=your_api_key_here
FINEXLY_BASE_URL=https://api.finexly.com/v1Load environment variables in your main application:
require('dotenv').config();
const axios = require('axios');
const express = require('express');
const app = express();
const API_KEY = process.env.FINEXLY_API_KEY;
const BASE_URL = process.env.FINEXLY_BASE_URL;Basic API Calls with Async/Await
Here's the simplest approach to fetch currency rates:
async function getExchangeRate(baseCurrency, targetCurrency) {
try {
const url = `${BASE_URL}/latest`;
const response = await axios.get(url, {
params: {
base: baseCurrency,
currencies: targetCurrency,
apikey: API_KEY
}
});
return response.data.rates[targetCurrency];
} catch (error) {
console.error(`Error fetching rate for ${baseCurrency}/${targetCurrency}:`, error.message);
throw error;
}
}
// Usage
(async () => {
const rate = await getExchangeRate('USD', 'EUR');
console.log(`1 USD = ${rate} EUR`);
})();This works for simple scripts, but production applications need more sophisticated patterns. Let's build on this foundation.
Building an Express Middleware for Currency Conversion
Create a reusable middleware that adds currency conversion to your Express application:
const currencyMiddleware = async (req, res, next) => {
// Make conversion functions available throughout the request
req.convertCurrency = async (amount, from, to) => {
try {
const rate = await getExchangeRate(from, to);
return (amount * rate).toFixed(2);
} catch (error) {
throw new Error(`Currency conversion failed: ${error.message}`);
}
};
next();
};
app.use(currencyMiddleware);
// Now use it in routes
app.post('/api/checkout', async (req, res) => {
const { amount, currency } = req.body;
try {
const amountInUSD = await req.convertCurrency(amount, currency, 'USD');
res.json({
originalAmount: amount,
currency: currency,
amountInUSD: amountInUSD
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});Caching Strategies for Performance
Calling the API every time becomes expensive and slow. Implement intelligent caching to reduce API calls and improve response times.
In-Memory Cache with TTL
For simple applications, in-memory cache with time-to-live (TTL) works well:
class CurrencyCache {
constructor(ttlMinutes = 5) {
this.cache = {};
this.ttlMs = ttlMinutes * 60 * 1000;
}
generateKey(base, target) {
return `${base}_${target}`;
}
get(base, target) {
const key = this.generateKey(base, target);
const cached = this.cache[key];
if (!cached) return null;
if (Date.now() > cached.expires) {
delete this.cache[key];
return null;
}
return cached.rate;
}
set(base, target, rate) {
const key = this.generateKey(base, target);
this.cache[key] = {
rate: rate,
expires: Date.now() + this.ttlMs
};
}
clear() {
this.cache = {};
}
}
const cache = new CurrencyCache(5); // 5-minute TTL
async function getExchangeRateCached(base, target) {
// Check cache first
const cachedRate = cache.get(base, target);
if (cachedRate !== null) {
console.log(`Cache hit: ${base}/${target}`);
return cachedRate;
}
// Fetch from API
const rate = await getExchangeRate(base, target);
cache.set(base, target, rate);
return rate;
}Error Handling and Retry Logic
Production applications must handle API failures gracefully:
async function getExchangeRateWithRetry(base, target, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const rate = await getExchangeRateCached(base, target);
return rate;
} catch (error) {
if (attempt === maxRetries) {
throw new Error(`Failed to fetch ${base}/${target} after ${maxRetries} attempts`);
}
// Exponential backoff: wait 1s, 2s, 4s between retries
const delayMs = Math.pow(2, attempt - 1) * 1000;
console.warn(`Attempt ${attempt} failed, retrying in ${delayMs}ms...`);
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
}
app.get('/api/rate', async (req, res) => {
const { base, target } = req.query;
try {
const rate = await getExchangeRateWithRetry(base, target);
res.json({ base, target, rate });
} catch (error) {
res.status(503).json({
error: 'Exchange rate service temporarily unavailable',
details: error.message
});
}
});Production-Ready Currency Conversion Service Class
Here's a complete, production-grade service encapsulating all the patterns above:
class CurrencyService {
constructor(apiKey, baseUrl, cacheTtlMinutes = 5) {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
this.cache = new CurrencyCache(cacheTtlMinutes);
this.rateLimiter = { lastCall: 0, minInterval: 100 }; // 100ms between API calls
}
async _rateLimitedFetch(url, params) {
// Prevent API rate limiting by spacing out calls
const now = Date.now();
const timeSinceLastCall = now - this.rateLimiter.lastCall;
if (timeSinceLastCall < this.rateLimiter.minInterval) {
await new Promise(resolve =>
setTimeout(resolve, this.rateLimiter.minInterval - timeSinceLastCall)
);
}
this.rateLimiter.lastCall = Date.now();
return axios.get(url, { params });
}
async getRate(base, target, maxRetries = 3) {
// Try cache first
const cached = this.cache.get(base, target);
if (cached !== null) return cached;
// Fetch with retries
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const url = `${this.baseUrl}/latest`;
const response = await this._rateLimitedFetch(url, {
base: base,
currencies: target,
apikey: this.apiKey
});
const rate = response.data.rates[target];
this.cache.set(base, target, rate);
return rate;
} catch (error) {
if (attempt === maxRetries) throw error;
const delayMs = Math.pow(2, attempt - 1) * 1000;
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
}
async convertAmount(amount, fromCurrency, toCurrency) {
const rate = await this.getRate(fromCurrency, toCurrency);
return parseFloat((amount * rate).toFixed(2));
}
clearCache() {
this.cache.clear();
}
}
// Initialize service
const currencyService = new CurrencyService(
process.env.FINEXLY_API_KEY,
process.env.FINEXLY_BASE_URL,
5 // 5-minute cache
);
// Use in Express app
app.post('/api/convert', async (req, res) => {
const { amount, from, to } = req.body;
try {
const converted = await currencyService.convertAmount(amount, from, to);
res.json({
original: { amount, currency: from },
converted: { amount: converted, currency: to }
});
} catch (error) {
res.status(500).json({ error: 'Conversion failed' });
}
});Performance Tips for Node.js Currency Applications
Batch Multiple Pairs: Instead of fetching rates one pair at a time, request multiple pairs in a single API call. Finexly supports this via the pairs parameter.
Use Connection Pooling: Node.js maintains HTTP connections efficiently, but for high-volume applications, configure axios to reuse connections:
const agent = new axios.Agent({ keepAlive: true });
axios.defaults.httpAgent = agent;Monitor Cache Hit Rates: Track how often your cache succeeds to optimize TTL:
class CurrencyCache {
constructor(ttlMinutes = 5) {
// ... existing code ...
this.stats = { hits: 0, misses: 0 };
}
get(base, target) {
const key = this.generateKey(base, target);
const cached = this.cache[key];
if (cached && Date.now() <= cached.expires) {
this.stats.hits++;
return cached.rate;
}
this.stats.misses++;
return null;
}
getHitRate() {
const total = this.stats.hits + this.stats.misses;
return total === 0 ? 0 : ((this.stats.hits / total) * 100).toFixed(1);
}
}Next Steps
- Review Finexly's complete documentation for all API endpoints
- Check pricing to understand rate limits and upgrade paths
- Explore real-time dashboards with REST vs WebSocket comparison
- See how to compare Finexly with other APIs
- For e-commerce implementations, see multi-currency pricing guide
This guide provides production-ready patterns that scale from small prototypes to high-volume applications handling thousands of currency conversion requests per minute. The combination of intelligent caching, rate limiting, retry logic, and error handling ensures reliability while keeping API costs low.
Explore More
Vlado Grigirov
Senior Currency Markets Analyst & Financial Strategist
Vlado Grigirov is a senior currency markets analyst and financial strategist with over 14 years of experience in foreign exchange markets, cross-border finance, and currency risk management. He has wo...
View full profile →