A full-stack application for tracking real-time currency exchange rates with intelligent caching, multi-source data aggregation, and a modern UI.
AnchorFX provides reliable, up-to-date currency exchange rates by:
- Fetching data from multiple API sources in parallel
- Intelligently resolving conflicts between sources
- Caching data with background refresh
- Gracefully degrading when sources are unavailable
- Displaying status clearly with visual indicators
- Frontend: https://currency-exchange-rate-tracker-two.vercel.app/
- Backend: https://currency-exchange-rate-tracker.onrender.com
- Rates API: https://currency-exchange-rate-tracker.onrender.com/api/rates
The main problem is that free-tier forex APIs don't always work, which makes users lose faith before they switch to paid. A layer of reliability on top of free APIs, not better ones, will fix the problem.
My first idea was to get data from three different APIs at once, store it heavily, and let the user know how up-to-date the data is. If all three APIs fail at once, instead of a hard error, show old cached data with a warning."Stale but visible" is better for user experience than "broken and empty."
The caching strategy (5-minute TTL, 4-minute background pre-refresh) means that most requests get data that's only seconds old right away, even though the APIs update every day. This makes the UI feel "live" without putting too much stress on the APIs.
The interesting problem was how to resolve conflicts: when three APIs give you slightly different rates, do you choose one or average them? When timestamps are close (they're all coming from the same market), I chose to average them. When timestamps are very different, I chose to use the newest source. This stops you from accidentally mixing old data with new data.
I got rid of the currency converter, historical charts, user accounts, and dark mode. None of these solve the main issue, which is trust and reliability. Sent the least amount that answers the question, "Can I trust what I see?"
The application follows a multi-layered architecture:
- React Frontend - Fluent UI-based interface with auto-refresh
- Node.js Backend - Express server with caching and orchestration
- Cache Layer - 5-minute TTL with background refresh
- Parallel Fetch - Simultaneous API calls to 3 sources
- Response Resolver - Intelligent conflict resolution
- External APIs - Multiple free exchange rate sources
- Multi-source aggregation: Fetches from 3 APIs simultaneously
- Smart caching: 5-minute TTL with 4-minute background refresh
- Conflict resolution: Averages rates or uses freshest source
- Graceful degradation: Returns stale data if APIs fail
- RESTful API: Clean, documented endpoints
- Modern UI: Built with Fluent UI components
- Real-time updates: Auto-refreshes every 30 seconds
- Status indicators: Clear visual feedback (Live/Stale/Unavailable)
- Responsive design: Works on mobile, tablet, and desktop
- Error handling: User-friendly error states
- Node.js 20.19+ or 22.12+ and npm
- Clone the repository
cd currency-exchange-rate-tracker- Install backend dependencies
cd backend
npm install- Install frontend dependencies
cd ../frontend
npm install- Start the backend (Terminal 1)
cd backend
npm startBackend runs on http://localhost:3001
- Start the frontend (Terminal 2)
cd frontend
npm run devFrontend runs on http://localhost:5173
- Open your browser to http://localhost:5173
currency-exchange-rate-tracker/
├── backend/
│ ├── server.js # Express server
│ ├── fetcher.js # API fetching logic
│ ├── resolver.js # Conflict resolution
│ ├── cache.js # Caching layer
│ ├── package.json
│ └── README.md
│
├── frontend/
│ ├── src/
│ │ ├── components/ # React components
│ │ ├── services/ # API client
│ │ ├── App.jsx # Main app
│ │ └── main.jsx # Entry point
│ ├── package.json
│ └── README.md
│
└── README.md # This file
Fetch current exchange rates
Response (Live)
{
"status": "live",
"base": "USD",
"rates": {
"EUR": 0.84073,
"GBP": 0.72856,
"JPY": 154.313647,
...
},
"timestamp": 1769904151000,
"sources": ["exchangerate-api", "open.er-api", "frankfurter"],
"resolution": "averaged",
"cached": true
}Response (Stale)
{
"status": "stale",
"warning": "Data may be outdated...",
...
}Response (Unavailable - 503)
{
"status": "unavailable",
"error": "Exchange rate data is temporarily unavailable...",
"retryAfter": 30
}Health check with cache statistics
List of supported currencies
Base: USD (US Dollar)
Targets:
- EUR (Euro)
- GBP (British Pound)
- JPY (Japanese Yen)
- CAD (Canadian Dollar)
- AUD (Australian Dollar)
- CHF (Swiss Franc)
- CNY (Chinese Yuan)
- INR (Indian Rupee)
- MXN (Mexican Peso)
- BRL (Brazilian Real)
- exchangerate-api -
https://open.exchangerate-api.com - open.er-api -
https://open.er-api.com - frankfurter -
https://api.frankfurter.app
All APIs are:
- Free and open
- No authentication required
- Rate-limit friendly
- Cache TTL: 5 minutes
- Refresh Threshold: 4 minutes
- Behavior:
- 0-4 min: Return cache immediately
- 4-5 min: Return cache + trigger background refresh
- 5+ min: Fetch fresh data
- APIs fail: Return stale cache with warning
- No cache + APIs fail: Return 503 error
When multiple sources return data:
- Single source: Use directly
- Timestamps differ > 1 hour: Use freshest source only
- Timestamps within 1 hour: Average all rates
cd backend
npm run dev # Auto-reload with --watch flagcd frontend
npm run dev # Vite dev server with HMRBackend:
cd backend
# Already production-ready, just run: npm startFrontend:
cd frontend
npm run build # Build to dist/
npm run preview # Preview production buildThe project is deployed as two services:
- Frontend: Vercel serves the React/Vite app at https://currency-exchange-rate-tracker-two.vercel.app/
- Backend: Render runs the Express API at https://currency-exchange-rate-tracker.onrender.com
- API flow: Vercel frontend -> Render
/api/ratesbackend -> public exchange-rate APIs
Render free tier services may spin down after inactivity. The first request after a cold start can take longer than normal, while subsequent cached responses are fast.
The production frontend uses:
VITE_API_URL=https://currency-exchange-rate-tracker.onrender.comVerify deployment:
curl https://currency-exchange-rate-tracker.onrender.com/api/health
curl https://currency-exchange-rate-tracker.onrender.com/api/ratesBackend on Render
- Create a Render Web Service from the GitHub repository.
- Set Root Directory to
backend. - Set Build Command to
npm install. - Set Start Command to
node server.js.
Frontend on Vercel
- Create a Vercel project from the GitHub repository.
- Set Root Directory to
frontend. - Set
VITE_API_URLtohttps://currency-exchange-rate-tracker.onrender.com. - Deploy the production build.
Backend Alternatives:
- Railway - Docker-based deployment, free tier available
- Heroku - Classic PaaS (paid)
- AWS Lambda - Serverless (via adapter)
- DigitalOcean App Platform - Simple deployment
Frontend Alternatives:
- Netlify - Similar to Vercel
- Cloudflare Pages - Fast CDN
- GitHub Pages - Free static hosting
- AWS S3 + CloudFront - Scalable CDN
- Backend deployed with Render-provided
PORT - Frontend deployed with
VITE_API_URL=https://currency-exchange-rate-tracker.onrender.com - Live state verified on the deployed Vercel app
- Restrict CORS to the production frontend domain
- Configure custom domain (optional)
PORT=3001 # Server port (default: 3001)Local development:
VITE_API_URL=http://localhost:3001 # Local backend URLProduction:
VITE_API_URL=https://currency-exchange-rate-tracker.onrender.com # Production backend URL- Test Live State: Start both servers, verify green "LIVE" badge
- Test Stale State: Stop backend, wait 5+ minutes, restart frontend
- Test Unavailable State: Stop backend completely
- Test Auto-refresh: Watch status badge update every 30 seconds
# Test rates endpoint
curl http://localhost:3001/api/rates
# Test health check
curl http://localhost:3001/api/health
# Test currencies list
curl http://localhost:3001/api/currencies- Express 5.x
- Node-fetch 3.x
- CORS middleware
- React 19
- Vite 7.x
- Fluent UI (React Components)
- Fluent UI Icons
- Backend: <100ms response time (with cache)
- Frontend: ~100KB gzipped bundle
- API Timeout: 5 seconds per source
- Auto-refresh: 30 seconds interval
- Network timeouts: Gracefully skipped
- All APIs fail: Stale cache returned
- No cache available: 503 error with retry info
- Invalid JSON: Source skipped, logged
- Add more currency pairs
- Historical rate charts
- Currency conversion calculator
- Multiple base currency support
- Dark mode
- Rate change notifications
- Export to CSV/Excel
- WebSocket for real-time updates
- Structured error logging and API rate-limit monitoring
ISC
For issues or questions, please check:
Built with ❤️ using React, Node.js, and Fluent UI