Real-time cryptocurrency dashboard that displays live exchange rates for ETH/USDC, ETH/USDT and ETH/BTC with live charts and data updates.
This is a Turborepo monorepo containing:
- apps/
backend-nest: NestJS backend application with WebSocket supportfrontend-react: React frontend application with Vite
- packages/
shared: Shared TypeScript types and interfacesfrontend-core: Frontend core services, hooks and presentersbackend-core: Backend core services and domain logic
- Docker Setup: Docker and Docker Compose installed
- Manual Setup: Node.js >= 18.0.0 and pnpm >= 8.0.0
- Finnhub API key: Get one at https://finnhub.io/ (free tier available)
This is the fastest way to get the application running consistently across environments.
- Sign up for a free account at https://finnhub.io/
- Navigate to your dashboard and copy your API key
- The free tier supports up to 60 requests/minute
You have three options to set the FINNHUB_API_KEY:
Option 1: Using a .env file in the root (Recommended)
Create a .env file in the root directory:
cp .env.example .envEdit .env and add your Finnhub API key:
FINNHUB_API_KEY=your_finnhub_api_key_hereOption 2: Export as environment variable
export FINNHUB_API_KEY=your_finnhub_api_key_here
pnpm run docker:upOption 3: Pass directly in the command
FINNHUB_API_KEY=your_finnhub_api_key_here pnpm run docker:upNote: The Docker Compose configuration will read from:
- Environment variables in your shell (highest priority)
.envfile in the root directory (automatically read by Docker Compose if it exists)apps/backend-nest/.envfile (if it exists)
Important: The .env file in the root is optional. If you don't create it, you can still use environment variables from your shell. Docker Compose automatically reads the .env file in the root directory for variable substitution, so you don't need to specify it in the configuration.
# Build and start all services
pnpm run docker:up
# Or run in detached mode (background)
pnpm run docker:up:d
# Or build and start
pnpm run docker:up:buildAlternative: You can also use docker compose directly:
# Build and start all services
docker compose up --build
# Or run in detached mode
docker compose up -d --buildThe application will be available at:
- Frontend: http://localhost:5173
- Backend API: http://localhost:3000
- Health Check: http://localhost:3000/health
# Using pnpm script
pnpm run docker:down
# Or using docker compose directly
docker compose downIf you prefer to run the applications locally without Docker:
# Install pnpm if you don't have it
npm install -g pnpm@8.15.0
# Install all dependencies
pnpm install- Sign up at https://finnhub.io/
- Get your free API key from the dashboard
- Add it to the backend environment file (see below)
Backend (apps/backend-nest/.env):
PORT=3000
FINNHUB_API_KEY=your_finnhub_api_key_here
FRONTEND_URL=http://localhost:5173
LOG_LEVEL=INFO
DATA_DIR=./dataNote: The DATA_DIR environment variable specifies where hourly averages are persisted. Defaults to ./data if not specified. The directory will be created automatically if it doesn't exist.
Frontend (apps/frontend-react/.env):
VITE_WEBSOCKET_URL=http://localhost:3000Option 1: Run all apps together
pnpm devOption 2: Run individually
# Backend only
pnpm --filter @crypto-dashboard/backend-nest dev
# Frontend only
pnpm --filter @crypto-dashboard/frontend-react devThe applications will be available at:
- Frontend: http://localhost:5173
- Backend: http://localhost:3000
Build all packages and apps:
pnpm buildOr build for production with Docker:
docker-compose buildcrypto-dashboard/
├── apps/
│ ├── backend-nest/ # NestJS backend
│ │ ├── src/
│ │ │ ├── finnhub/ # Finnhub WebSocket service
│ │ │ ├── exchange-rate/ # Exchange rate processing
│ │ │ └── websocket/ # WebSocket gateway
│ │ ├── Dockerfile
│ │ └── package.json
│ └── frontend-react/ # React frontend
│ ├── src/
│ │ └── components/ # Dashboard components
│ ├── Dockerfile
│ ├── nginx.conf
│ └── package.json
├── packages/
│ ├── shared/ # Shared types
│ ├── frontend-core/ # Frontend services and presenters
│ └── backend-core/ # Backend services and domain logic
├── docker-compose.yml # Docker Compose configuration
├── turbo.json # Turborepo config
├── pnpm-workspace.yaml # pnpm workspace config
└── package.json # Root package.json
- ✅ Real-time exchange rate updates via WebSocket
- ✅ Live charts for ETH/USDC, ETH/USDT, and ETH/BTC
- ✅ Hourly average calculations with file-based persistence
- ✅ Automatic reconnection on connection failures
- ✅ Connection status indicators
- ✅ Error handling and logging
- ✅ Docker support for consistent deployments
- ✅ Health check endpoint with Finnhub API key validation
- Monorepo: Turborepo
- Package Manager: pnpm
- Backend: NestJS, TypeScript, Socket.IO
- Frontend: React, TypeScript, Vite, Recharts
- Real-time: WebSocket (Socket.IO)
- External API: Finnhub.io
- Containerization: Docker, Docker Compose
pnpm dev: Start all apps in development modepnpm build: Build all packages and appspnpm lint: Run linting on all packagespnpm clean: Clean all build outputspnpm docker:up: Start services with Docker Composepnpm docker:down: Stop Docker Compose servicespnpm docker:build: Build Docker images
Using pnpm scripts (recommended):
# Start services
pnpm run docker:up
# Start in detached mode with build
pnpm run docker:up:d
# Build and start
pnpm run docker:up:build
# Stop services
pnpm run docker:down
# Build images
pnpm run docker:build
# View logs
pnpm run docker:logs
# View logs for specific service
pnpm run docker:logs:backend
pnpm run docker:logs:frontend
# Development mode (with hot reload)
pnpm run docker:devUsing docker compose directly:
# Start services
docker compose up
# Start in detached mode
docker compose up -d
# Rebuild and start
docker compose up --build
# Stop services
docker compose down
# View logs
docker compose logs -f
# View logs for specific service
docker compose logs -f backend
docker compose logs -f frontendThe /health endpoint validates the application status and the Finnhub API key configuration.
GET /health
This endpoint performs a comprehensive health check that includes:
- Application status verification
- Finnhub API key presence validation
- Finnhub API key validity check (makes a test request to Finnhub API)
The endpoint is used by Docker Compose health checks to ensure the backend is ready before starting dependent services.
- 200 OK: Application is healthy and Finnhub API key is valid
- 503 Service Unavailable: Application is running but Finnhub API key is missing or invalid
{
"status": "ok",
"timestamp": "2024-01-15T10:30:00.000Z",
"finnhub": {
"status": "ok"
}
}{
"status": "error",
"timestamp": "2024-01-15T10:30:00.000Z",
"finnhub": {
"status": "error",
"error": {
"code": "FINNHUB_API_KEY_MISSING",
"message": "Finnhub API key is not configured"
}
}
}The endpoint can return the following error codes:
| Code | Description |
|---|---|
FINNHUB_API_KEY_MISSING |
The FINNHUB_API_KEY environment variable is not set or is empty |
FINNHUB_API_KEY_INVALID_FORMAT |
The API key format is invalid (less than 10 characters) |
FINNHUB_API_KEY_INVALID |
The API key was rejected by Finnhub API (401 Unauthorized) |
FINNHUB_API_KEY_ERROR |
The Finnhub API returned an error (non-401 status code) |
FINNHUB_API_KEY_TIMEOUT |
The validation request to Finnhub API timed out (5 seconds) |
Check health status:
curl http://localhost:3000/healthCheck health status with verbose output:
curl -v http://localhost:3000/healthUsing in scripts:
# Check if service is healthy
if curl -f http://localhost:3000/health > /dev/null 2>&1; then
echo "Service is healthy"
else
echo "Service is unhealthy"
exit 1
fi- Validation Method: The endpoint makes a test HTTP request to
https://finnhub.io/api/v1/quote?symbol=AAPL&token={apiKey}to validate the API key - Timeout: 5 seconds maximum wait time for the validation request
- Network Errors: If a network error occurs (not timeout), the endpoint assumes the key is valid to avoid false negatives due to temporary network issues
- Performance: The health check typically completes in < 1 second when the API key is valid
The health check is automatically used by Docker Compose:
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40sThis ensures that:
- The frontend service only starts after the backend is healthy
- The backend service is restarted if it becomes unhealthy
- Container orchestration can properly manage service dependencies
Port already in use:
# Check what's using the port
lsof -i :3000
lsof -i :5173
# Stop existing containers
docker-compose down
# Or change ports in docker-compose.ymlBuild fails:
# Clean Docker cache
docker system prune -a
# Rebuild without cache
docker-compose build --no-cacheContainer won't start:
# Check logs
docker-compose logs backend
docker-compose logs frontend
# Check if environment variables are set
docker-compose configpnpm not found:
npm install -g pnpm@8.15.0Build errors:
# Clean and reinstall
pnpm clean
rm -rf node_modules
pnpm install
pnpm buildBackend won't connect to Finnhub:
- Verify
FINNHUB_API_KEYis set correctly - Check if API key is valid at Finnhub Dashboard
- Free tier has rate limits (60 requests/minute)
- Use the
/healthendpoint to verify API key status:curl http://localhost:3000/health
Frontend can't connect to backend:
- Ensure backend is running on port 3000
- Check
VITE_WEBSOCKET_URLin frontend.env - Verify CORS settings in backend
WebSocket connection issues:
- Check browser console for errors
- Verify backend WebSocket gateway is running
- Check network tab for WebSocket connection status
This project follows Domain-Driven Design (DDD) and Clean Architecture principles. The architecture is organized in layers with clear dependency rules ensuring maintainability, testability, and separation of concerns.
- Domain Layer: Pure business logic, no external dependencies
- Application Layer: Use cases and orchestration, depends only on domain
- Infrastructure Layer: External concerns (APIs, storage), implements application ports
- Interface Layer: User-facing (NestJS controllers, React components)
Dependency Rule: Dependencies point inward only. Domain has zero dependencies on outer layers.
- Architecture Documentation: Comprehensive architecture guide
- Real-time Architecture: WebSocket and streaming details
- Data Persistence: Storage strategies and limitations
- Backend: NestJS WebSocket Gateway using Socket.IO for bidirectional communication
- Frontend: Socket.IO client for real-time updates
- Data Flow: Finnhub WebSocket → NestJS → Socket.IO Gateway → React Frontend
- See: Real-time Architecture Documentation
- Automatic reconnection with exponential backoff (backend)
- Socket.IO built-in reconnection (frontend)
- Connection status indicators in UI
- Comprehensive logging on backend
- Graceful error handling for API failures
- Monorepo: Turborepo with pnpm workspaces
- Packages: Shared types, backend-core, frontend-core
- Layers: Clear separation (Domain, Application, Infrastructure, Interface)
- Type Safety: Shared TypeScript types across packages
- See: Architecture Documentation
- Hourly Averages: Persisted to JSON file (
data/hourly-averages.json) using file system repository - Exchange Rates: Kept in memory for real-time performance (last hour, max 3600 entries per pair)
- Persistence Strategy: File-based storage with atomic write operations to prevent data corruption
- Data Directory: Configurable via
DATA_DIRenvironment variable (defaults to./data) - Docker: Data directory is mounted as a volume to persist data across container restarts
- Limitations: Single-instance only, no concurrent write protection, see Data Persistence Documentation for details
- Repository Pattern: Abstract data access (domain defines interface, infrastructure implements)
- Adapter Pattern: Adapt external services to internal interfaces (Finnhub, Socket.IO)
- Observer Pattern (RxJS): Reactive data streams for decoupled communication
- Use Case Pattern: Encapsulate business workflows in single-purpose classes
- Presenter Pattern: Transform domain data for UI consumption (frontend)
See: Architecture Documentation for detailed pattern explanations