A production-grade, database-agnostic FastAPI service that fetches daily currency conversion rates for all supported currencies. The service automatically fetches rates twice daily (00:00 and 12:00 UTC) and also on startup if no rates exist for the current day, ensuring data is always available. It stores only the latest rates and is fully compatible with Supabase Postgres, local Postgres, or any SQL database.
- ✅ Database Agnostic: Works with Supabase, local Postgres, Neon, Railway, RDS, or any PostgreSQL database
- ✅ Automatic Scheduling: Fetches rates twice daily (00:00 and 12:00 UTC) and on startup if needed
- ✅ Flexible Currency Selection: Enter any currency code to get its value converted to all other currencies in JSON format; simple and intuitive API
- ✅ Latest Rates Only: Automatically removes old rates, keeping only the most recent data
- ✅ Fully Async: Built with async/await throughout for optimal performance
- ✅ Production Ready: Includes health checks, error handling, and proper logging
- ✅ Docker Support: Ready for containerization and deployment
- ✅ Type Safe: Fully typed with Pydantic v2 and SQLAlchemy 2.0
- FastAPI - Modern, fast web framework
- SQLAlchemy 2.0 - Async ORM with async engine and sessions
- asyncpg - High-performance async PostgreSQL driver
- httpx - Async HTTP client for API requests
- APScheduler - Async job scheduler
- Pydantic v2 - Data validation and settings management
- Alembic - Database migrations (optional)
.
├── app/
│ ├── main.py # FastAPI application entry point
│ ├── core/
│ │ ├── config.py # Configuration and settings
│ │ └── database.py # Database connection and session management
│ ├── models/
│ │ └── exchange_rate.py # SQLAlchemy model
│ ├── services/
│ │ └── fx_service.py # Currency rate fetching service
│ ├── scheduler/
│ │ └── jobs.py # Scheduled jobs
│ └── api/
│ └── routes.py # API endpoints
├── requirements.txt
├── Dockerfile
├── docker-compose.yml
├── .env.example
└── README.md
- Python 3.11+
- PostgreSQL database (local or cloud-hosted)
- ExchangeRate API key (Get one here)
- Clone the repository
git clone <repository-url>
cd "Daily Currency Conversion Rate Fetcher"- Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate- Install dependencies
pip install -r requirements.txt- Configure environment variables
cp .env.example .envEdit .env with your configuration:
DATABASE_URL=postgresql+asyncpg://username:password@localhost:5432/currency_rates
FX_API_KEY=your_api_key_here
FX_API_BASE_URL=https://v6.exchangerate-api.com/v6
APP_ENV=development- Run the application
python -m uvicorn app.main:app --reloadThe service will be available at http://localhost:8000
- API Documentation:
http://localhost:8000/docs - Health Check:
http://localhost:8000/api/v1/health
- Install PostgreSQL (if not already installed)
# macOS
brew install postgresql
brew services start postgresql
# Ubuntu/Debian
sudo apt-get install postgresql postgresql-contrib
sudo systemctl start postgresql- Create database
createdb currency_rates- Update
.env
DATABASE_URL=postgresql+asyncpg://postgres:password@localhost:5432/currency_rates-
Create a Supabase project at supabase.com
-
Get your connection string
- Go to Project Settings → Database
- Copy the connection string (either Transaction Pooler or Direct Connection)
- Format:
postgresql+asyncpg://postgres:[YOUR-PASSWORD]@db.[PROJECT-REF].supabase.co:5432/postgres
-
Update
.env
DATABASE_URL=postgresql+asyncpg://postgres:your_password@db.xxxxx.supabase.co:5432/postgresNote: The service treats Supabase as a standard PostgreSQL database. No Supabase SDK is required.
The service works with any PostgreSQL-compatible database:
- Neon:
postgresql+asyncpg://user:pass@ep-xxx.us-east-1.aws.neon.tech/neondb - Railway: Use the provided PostgreSQL connection string
- AWS RDS: Standard PostgreSQL connection string
- Google Cloud SQL: PostgreSQL connection string
GET /api/v1/healthReturns service and database connectivity status.
Response:
{
"status": "healthy",
"service": "currency-rate-fetcher",
"database": "connected"
}GET /api/v1/rates/{currency}
GET /api/v1/rates?currency=EURReturns conversion rates from the specified currency to all other available currencies.
Enter a currency code (e.g., EUR, USD, GBP) and get its value converted to all other currencies in JSON format.
Examples:
# Get EUR converted to all other currencies
GET /api/v1/rates/EUR
# Get USD converted to all other currencies
GET /api/v1/rates/USD
# Get GBP converted to all other currencies (using query parameter)
GET /api/v1/rates?currency=GBP
# Get stored rates (default: USD base) - no parameter
GET /api/v1/ratesResponse (currency conversions):
{
"from_currency": "EUR",
"conversions": {
"USD": 1.18,
"GBP": 0.86,
"JPY": 129.79,
"AUD": 1.65,
"CAD": 1.48,
...
},
"fetched_at": "2026-01-24T16:30:00.000000",
"total_currencies": 162,
"message": "1 EUR equals the following amounts in other currencies:"
}Response (stored rates - default USD):
{
"from_currency": "USD",
"conversions": {
"EUR": 0.85,
"GBP": 0.73,
"JPY": 110.25,
"AUD": 1.40,
"CAD": 1.26,
...
},
"fetched_at": "2026-01-24T12:00:00.000000",
"total_currencies": 162,
"message": "1 USD equals the following amounts in other currencies:",
"source": "stored"
}Usage: Simply enter the currency code you want to convert from, and the API returns a JSON object with all conversion rates to other currencies, making it easy to select and use the rates you need.
- Configure environment
cp .env.example .env
# Edit .env with your settings- Start services
docker-compose up -dThis will start:
- The FastAPI application on port 8000
- A PostgreSQL database on port 5432
- View logs
docker-compose logs -f currency-fetcher- Stop services
docker-compose down- Build the image
docker build -t currency-fetcher .- Run the container
docker run -d \
--name currency-fetcher \
-p 8000:8000 \
--env-file .env \
currency-fetcherThe service automatically fetches currency rates twice daily:
- 00:00 UTC (midnight)
- 12:00 UTC (noon)
Startup Behavior:
- When the server starts, it automatically checks if rates exist for today
- If no rates are found for the current day, it immediately fetches and stores them
- This ensures the API has data available right away, even if the server is started outside the scheduled times
The scheduler:
- Starts automatically when the application starts
- Is idempotent (no duplicate jobs on reload)
- Handles missed executions gracefully (5-minute grace period)
- Only stores the latest rates (deletes previous entries)
To manually trigger a rate fetch (for testing), you can:
- Use the scheduler directly (in Python):
from app.scheduler.jobs import currency_job
await currency_job.fetch_and_store_rates()- Or restart the service - the first fetch happens on startup
| Variable | Description | Required | Default |
|---|---|---|---|
DATABASE_URL |
PostgreSQL connection string | Yes | - |
FX_API_KEY |
ExchangeRate API key | Yes | - |
FX_API_BASE_URL |
Base URL for FX API | No | https://v6.exchangerate-api.com/v6 |
APP_ENV |
Application environment | No | development |
The service creates a single table:
| Column | Type | Description |
|---|---|---|
id |
UUID (String) | Primary key |
base_currency |
VARCHAR | Base currency (e.g., "USD") |
rates |
JSON/JSONB | Dictionary of currency rates |
fetched_at |
TIMESTAMP WITH TIME ZONE | When rates were fetched (UTC) |
Note: Only one row exists at any time. New fetches replace the previous row.
APP_ENV=development python -m uvicorn app.main:app --reloadThe codebase follows these principles:
- Fully async: All I/O operations use async/await
- Type hints: All functions and classes are fully typed
- Clean architecture: Separation of concerns (models, services, API, scheduler)
- Error handling: Comprehensive error handling and logging
- Database agnostic: No vendor-specific code
# Health check
curl http://localhost:8000/api/v1/health
# Get all rates
curl http://localhost:8000/api/v1/rates
# Get specific currency
curl http://localhost:8000/api/v1/rates/EUR-
Environment Variables: Use secure secret management (e.g., environment variables, secrets manager)
-
Database: Use a managed PostgreSQL service (Supabase, Neon, RDS, etc.)
-
CORS: Update CORS settings in
app/main.pyfor production:
app.add_middleware(
CORSMiddleware,
allow_origins=["https://yourdomain.com"], # Specific origins
allow_credentials=True,
allow_methods=["GET"],
allow_headers=["*"],
)-
Logging: Configure proper logging for production (e.g., structured logging, log aggregation)
-
Monitoring: Set up health check monitoring for the
/api/v1/healthendpoint -
Scaling: The service is stateless and can be horizontally scaled
- Railway: Connect your GitHub repo and set environment variables
- Render: Deploy from GitHub with PostgreSQL addon
- Fly.io: Use
flyctlto deploy - AWS/GCP/Azure: Use container services (ECS, Cloud Run, Container Instances)
- Heroku: Use Heroku Postgres and deploy via Git
- Verify
DATABASE_URLformat:postgresql+asyncpg://user:pass@host:port/db - Check database is accessible from your network
- For Supabase: Ensure you're using the correct connection string (pooler vs direct)
- Verify
FX_API_KEYis set correctly - Check API key is valid and has sufficient quota
- Review logs for API error messages
- Check application logs for scheduler startup messages
- Verify the scheduler started: Look for "Currency rate scheduler started" in logs
- Ensure the application is running continuously (not just for a single request)
- Wait for the scheduled fetch (00:00 or 12:00 UTC)
- Manually trigger a fetch (see Scheduling section)
- Check logs for fetch errors
This project is open-source and available for use under the MIT License.
Contributions are welcome! Please feel free to submit a Pull Request.
For issues, questions, or contributions, please open an issue on the repository.