FinDash is a simulated digital wallet and payment platform that lets users:
- Sign up and log in securely (JWT + HttpOnly cookies)
- Check wallet balances and transaction history
- Send money to other users
- Manage contacts and view analytics
Built with Vue 3 frontend, Spring Boot microservices, PostgreSQL, gRPC, and Kafka for messaging.
Important
For Docker (recommended):
- Docker Desktop or Docker Engine (20.10+)
- Docker Compose (2.0+)
For local development without Docker:
- Java 21+ (JDK)
- Maven 3.9+
- Node.js 18+
- Docker Compose (still needed for Postgres and Kafka)
- Frontend (Vue 3 + Vite) - User interface served by Nginx on port 3000
- API Gateway (Spring Cloud Gateway + Resilience4J) - Routes requests, handles CORS, circuit breaking on port 8080
- Wallet Service (Spring Boot) - User accounts, wallets, balances on port 8081 (REST) and port 9091 (gRPC)
- Transaction Service (Spring Boot) - Payment processing, transfers on port 8082
- Kafka + Zookeeper - Event streaming for async transaction processing
- PostgreSQL - Single database
findashwith user, wallet, and transaction tables
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β User Browser β
βββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β HTTP
βΌ
βββββββββββββββββββββββββ
β Frontend (Vue 3) β
β Nginx :3000 β
βββββββββββββ¬ββββββββββββ
β HTTP REST
βΌ
βββββββββββββββββββββββββ
β API Gateway :8080 β
β - Routes requests β
β - Circuit breakers β
β - CORS, Rate limit β
βββββββ¬ββββββββββββββ¬ββββ
β β HTTP Routes
βββββββββββββββββ βββββββββββββββββ
β /api/wallet/** /api/transaction/**
βΌ βΌ
βββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββ
β Wallet Service β β Transaction Service β
β :8081 (REST) :9091 (gRPC) βββββββββ :8082 (REST) β
β β gRPC β β
β - User accounts β :9091 β - Transfer logic β
β - Wallet CRUD β β - gRPC Client β
β - gRPC Server β β - Saves tx to DB β
β (checkSufficientBalance) β ββββββββββββββββ¬ββββββββββββββββ
β - Kafka consumer β β Publishes
ββββββββ¬βββββββββββββββββββββββββββ βΌ
β ββββββββββββββββββββββββ
β Reads β Kafka :9092 β
β β Topic: β
β β "transactions" β
β ββββββββββββββββββββββββ
β Consumes β
βββββββββββββββββββββββββββββββββββββββββββββββββ
β
β Updates balance
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PostgreSQL :5432 β
β Database: findash β
β ββββββββββββ ββββββββββββ ββββββββββββββββββββ β
β β users β β wallets β β transactions β β
β ββββββββββββ ββββββββββββ ββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-
Frontend β Gateway (HTTP/REST)
- Users register and log in via the Vue UI
- All API calls go through the gateway at
http://localhost:8080
-
Gateway β Services (HTTP/REST with circuit breakers)
- Gateway routes
/api/wallet/**to Wallet Service - Gateway routes
/api/transaction/**to Transaction Service - If a service is unavailable, the gateway returns a 503 fallback
- Gateway routes
-
Transaction β Wallet (gRPC - synchronous balance validation)
- Before processing a transfer, Transaction Service calls Wallet Service via gRPC
- The
checkSufficientBalanceRPC verifies sender has enough funds - If balance is insufficient, transaction is rejected immediately with 400 Bad Request
- gRPC provides fast, type-safe validation before any database writes
-
Transaction β Kafka (Event streaming - async)
- After validation, Transaction Service saves the transaction to DB and publishes an event to Kafka
- The event includes sender ID, receiver ID, and amount
-
Kafka β Wallet (Consumer - async)
- Wallet Service listens on the
transactionstopic - It consumes events and updates balances in the database
- Wallet Service listens on the
-
Services β Database (JDBC with HikariCP pooling)
- Both services connect to the same PostgreSQL database
- Wallet owns
usersandwalletstables - Transaction owns the
transactionstable
- Backend: Java 21, Spring Boot 3, Spring Cloud Gateway, Resilience4J, Micrometer
- RPC: gRPC 1.63.0, Protocol Buffers 3
- Messaging: Apache Kafka
- Data: PostgreSQL, HikariCP
- Security: JWT (HttpOnly Cookies), BCrypt, Jakarta Validation, rate limiting (Bucket4j), CORS with credentials
- Frontend: Vue 3, Vite, Tailwind, Axios
- Containers: Docker, Docker Compose
api-gateway/src/main/java/com/finstream/gateway/ApiGatewayApplication.java- Gateway entry pointapi-gateway/src/main/resources/application.yml- Route definitions, circuit breaker config, CORSapi-gateway/src/main/java/com/finstream/gateway/controller/FallbackController.java- Fallback responses when services down
wallet-service/src/main/java/com/finstream/wallet/controller/UserController.java- Registration, login, user managementwallet-service/src/main/java/com/finstream/wallet/controller/WalletController.java- Wallet balance operationswallet-service/src/main/java/com/finstream/wallet/grpc/GrpcWalletService.java- gRPC server for balance validationwallet-service/src/main/java/com/finstream/wallet/kafka/TransactionEventConsumer.java- Kafka listener for balance updateswallet-service/src/main/java/com/finstream/wallet/security/JwtUtil.java- JWT token generation/validationwallet-service/src/main/resources/application.yml- DB, Kafka, JWT, gRPC config
transaction-service/src/main/java/com/finstream/transaction/controller/TransactionController.java- Transfer endpointstransaction-service/src/main/java/com/finstream/transaction/service/TransactionOrchestrator.java- Transaction processing, gRPC validation, and Kafka publishingtransaction-service/src/main/java/com/finstream/transaction/grpc/WalletGrpcClient.java- gRPC client for wallet service callstransaction-service/src/main/resources/application.yml- DB, Kafka, and gRPC client config
common/src/main/proto/service.proto- Protocol Buffers definition for gRPC services
frontend/src/views/Login.vue- Login/register UIfrontend/src/views/Dashboard.vue- User dashboard with balance and transfersfrontend/src/main.ts- Axios interceptors for JWT authfrontend/nginx.conf- Nginx config for SPA routing
docker-compose.yml- All services orchestration with health checks and resource limitsdocker/postgres/init.sql- Database schema creation
Copy the template and set values:
Copy-Item .env.example .env
# Edit .env and set:
# DB_USERNAME=findash_user
# DB_PASSWORD=ChangeMe_1234!
# JWT_SECRET=long-random-secret
# WALLET_SEED_DEMO=falseNote
For production, use a very long JWT_SECRET and managed secrets.
First time or after errors:
docker-compose up -d --buildIf you encounter Kafka/Zookeeper errors (NodeExistsException):
docker-compose down -v
docker-compose up -d- Frontend: http://localhost:3000
- API Gateway: http://localhost:8080
Tip
The -v flag removes volumes including Zookeeper state, which fixes session conflicts.
This project supports three different deployment methods with environment-specific configurations:
- Perfect for local development and testing
- All services in containers, single command to start
- Command:
docker-compose up -d - Access: http://localhost:3000
- Database: PostgreSQL container
- See: Docker-Compose Guide
- Full production-grade Kubernetes deployment
- Auto-scaling, rolling updates, load balancing
- Uses AWS RDS for managed PostgreSQL
- Command:
bash k8s/deploy.sh - Setup: Edit
k8s/.envwith your AWS info - See: k8s/README.md
- Provision entire AWS infrastructure automatically
- Creates VPC, EKS cluster, RDS database, and ECR registries
- Command:
terraform apply(ininfra/terraform/) - Setup: Update
terraform.tfvarswith your values - See: Terraform Configuration
All three methods use the same application code with environment-specific configuration:
| Component | Docker-Compose | Kubernetes | Terraform |
|---|---|---|---|
| Database | PostgreSQL container postgres:5432 |
RDS endpoint via ${DB_HOST} |
RDS created by Terraform |
| Credentials | docker-compose.yml defaults | K8s Secrets | terraform.tfvars ( |
| Frontend Port | 3000 (maps to 80) | 8080 (LoadBalancer) | 8080 via EKS |
# Build all services
mvn clean package -DskipTests
# Terminal 1: Start database and messaging
docker-compose up -d postgres zookeeper kafka
# Terminal 2: Wallet Service
mvn -pl wallet-service spring-boot:run
# Terminal 3: Transaction Service
mvn -pl transaction-service spring-boot:run
# Terminal 4: API Gateway
mvn -pl api-gateway spring-boot:run
# Terminal 5: Frontend (with hot reload)
cd frontend
npm install
npm run devAll requests go through the gateway at http://localhost:8080. Authentication is automatic via HttpOnly cookie.
POST /api/wallet/usersβ Register new userPOST /api/wallet/loginβ Log in (sets secure cookie)POST /api/wallet/logoutβ Log outGET /api/wallet/usersβ List all usersGET /api/wallet/{userId}/balanceβ Get balance (requires auth)
GET /api/transaction/history/{userId}β View transactions (requires auth)POST /api/transaction/transferβ Send money to another user (requires auth)
# Create two users
$u1 = @{ fullName = "Alice"; email = "alice@test.com"; password = "TestPass!123" } | ConvertTo-Json
$u2 = @{ fullName = "Bob"; email = "bob@test.com"; password = "TestPass!123" } | ConvertTo-Json
$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
Invoke-RestMethod -Uri "http://localhost:8080/api/wallet/users" -Method POST -ContentType "application/json" -Body $u1 -WebSession $session
Invoke-RestMethod -Uri "http://localhost:8080/api/wallet/users" -Method POST -ContentType "application/json" -Body $u2 -WebSession $session
# Log in as Alice
$login = @{ email = "alice@test.com"; password = "TestPass!123" } | ConvertTo-Json
$aliceLogin = Invoke-RestMethod -Uri "http://localhost:8080/api/wallet/login" -Method POST -ContentType "application/json" -Body $login -WebSession $session
# Get Bob's user ID
$users = Invoke-RestMethod -Uri "http://localhost:8080/api/wallet/users" -Method GET -WebSession $session
$bobId = ($users | Where-Object { $_.email -eq "bob@test.com" }).id
# Send $5 to Bob (need sender and receiver UUIDs)
$xfer = @{ senderId = $aliceLogin.id; receiverId = $bobId; amount = 5 } | ConvertTo-Json
Invoke-RestMethod -Uri "http://localhost:8080/api/transaction/transfer" -Method POST -ContentType "application/json" -Body $xfer -WebSession $sessionAuthentication: JWT tokens stored in secure HttpOnly cookies. Browser automatically sends with each request. Never stored in localStorage/sessionStorage.
Password: BCrypt hashing with 10+ salt rounds.
Rate Limiting: Max 5 login attempts per minute to prevent brute force.
CORS: Configured for localhost. Update for production domains only.
Database: All queries are parameterized (no SQL injection).
gRPC: Service-to-service communication using gRPC for synchronous balance validation. Currently configured with plaintext for local development.
Production Checklist:
- Set
Secureflag on cookies (requires HTTPS) - Use 256+ bit JWT secret in secure vault
- Enable TLS 1.3+ on all connections
- Configure TLS for gRPC (currently using plaintext for local dev)
- Update allowed CORS origins to production domains
- Review rate limit thresholds
- Implement gRPC authentication/authorization for service-to-service calls
- β
Centralized auth state via shared
useAuth()composable - β Single auth check on app startup (no race conditions)
- β
Removed duplicate functions (
formatRelativeTime,loadUser,handleLogout, etc.) - β Removed sessionStorage usage (only HttpOnly cookies for auth)
- β Contact persistence with localStorage (per-user keys)
- β Contact search filters out already-added contacts
- β Dark mode initialization prevents UI flashing
- β All components use proper TypeScript types
- β gRPC integration for synchronous balance validation
- β Dual-path architecture: gRPC for validation, Kafka for updates
When a user initiates a money transfer:
- User submits transfer via Vue frontend β API Gateway β Transaction Service
- gRPC validation (synchronous): Transaction Service calls Wallet Service via gRPC to verify sender has sufficient balance
- If insufficient: Transaction rejected immediately with 400 Bad Request
- If sufficient: Continue to step 3
- Save transaction: Transaction Service persists transaction record with status
COMPLETED - Publish event (asynchronous): Transaction Service publishes event to Kafka topic
transactions - Balance update (asynchronous): Wallet Service consumes Kafka event and updates both sender and receiver balances
This dual-path ensures:
- Fast validation: gRPC provides immediate feedback (< 100ms typically)
- Data consistency: Kafka ensures eventual consistency for balance updates
- Fault tolerance: If Kafka is down, transaction is still validated and saved; balance update happens when Kafka recovers