A microservices banking platform for managing accounts, transfers, and spending analytics. Built to learn microservices architecture patterns -- service decomposition, API gateway routing, circuit breakers, and distributed auth -- using a realistic banking domain.
Live Demo | Features | Architecture | Quick Start | API Reference | Tech Stack
The application is deployed and accessible at the following URLs:
| Service | URL |
|---|---|
| Frontend | https://frontend-two-virid-21.vercel.app |
| API Gateway | https://financeflow-api-kkuw.onrender.com |
| Auth Service | https://financeflow-auth.onrender.com |
| Account Service | https://financeflow-account.onrender.com |
| Transaction Service | https://financeflow-transaction.onrender.com |
| Analytics Service | https://financeflow-analytics.onrender.com |
Demo credentials: john.doe@example.com / password123
Note: Backend services are on Render's free tier and may take 30-60 seconds to wake up on first request.
I wanted to build something that goes beyond a basic CRUD app and actually deals with the kinds of problems you'd hit in a real banking system -- things like concurrent balance updates, atomic transfers, and keeping multiple services in sync.
The pessimistic locking on balance updates was something I had to debug for hours before getting right. Turns out it's easy to write a transfer endpoint that passes all your tests but breaks under concurrent load. I also underestimated how much work goes into a proper JWT auth flow with refresh token rotation -- the edge cases around token expiry and race conditions between tabs were tricky.
| Login | Dashboard |
|---|---|
![]() |
![]() |
| Accounts | Transactions |
|---|---|
![]() |
![]() |
| Analytics |
|---|
![]() |
|
|
+-------------------------------------------------------------------------+
| FRONTEND |
| React 18 + TypeScript + Material UI |
| Port: 3000 |
+-----------------------------------+-------------------------------------+
| HTTP/REST
v
+-------------------------------------------------------------------------+
| API GATEWAY |
| Spring Cloud Gateway (Port: 8080) |
| +------------+------------+------------+------------+ |
| | Routing | JWT Filter | CORS | Circuit | |
| | | | Config | Breaker | |
| +------------+------------+------------+------------+ |
+--------+-------------+-------------+-------------+----------------------+
| | | |
v v v v
+-----------+ +-----------+ +-----------+ +-----------+
| AUTH | | ACCOUNT | |TRANSACTION| | ANALYTICS |
| SERVICE | | SERVICE | | SERVICE | | SERVICE |
| (8081) | | (8082) | | (8083) | | (8084) |
+-----------+ +-----------+ +-----------+ +-----------+
| Register | | Create | | Deposit | | Spending |
| Login | | List | | Withdraw | | by Cat. |
| JWT Gen | | Balance | | Transfer | | Monthly |
| Refresh | | Update | | History | | Trends |
+-----+-----+ +-----+-----+ +-----+-----+ +-----+-----+
| | | |
+---------------+-------+-------+---------------+
|
+----------+----------+
v v
+-------------------+ +--------------------+
| PostgreSQL | | Redis |
| (Port: 5432) | | (Port: 6379) |
| | | |
| users | | Analytics cache |
| refresh_tokens | | TTL eviction |
| accounts | | LRU policy |
| transactions | | |
+-------------------+ +--------------------+
| Pattern | Implementation |
|---|---|
| API Gateway | Single entry point, JWT validation, circuit breakers, CORS |
| DTO Pattern | Separate domain models from API contracts |
| Repository Pattern | Abstract data access with Spring Data JPA |
| Service Layer | Business logic encapsulation |
| Global Exception Handling | Consistent error responses across services |
| Pessimistic Locking | @Lock(PESSIMISTIC_WRITE) for balance updates |
| Atomic Transactions | @Transactional for multi-account transfers |
- Docker Desktop (Download) - Make sure it's running
- 8GB+ free disk space
- Ports 3000, 5432, 8080-8084 available
# Clone the repository
git clone https://github.com/PohTeyToe/FinanceFlow.git
cd FinanceFlow
# Start all services (first run takes ~5 minutes)
docker compose up --build| Service | URL | Description |
|---|---|---|
| Frontend | http://localhost:3000 | React web application |
| API Gateway | http://localhost:8080 | Main API entry point |
Email: john.doe@example.com
Password: password123
Windows Users: Use
docker compose(with space), notdocker-compose(hyphen).
All endpoints are accessed through the API Gateway at http://localhost:8080.
POST /api/auth/register # Create new account
POST /api/auth/login # Get JWT tokens
POST /api/auth/refresh # Refresh access token
GET /api/auth/me # Get current user (Protected)Example: Login Request
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "john.doe@example.com", "password": "password123"}'Response:
{
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs...",
"expiresIn": 86400,
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe"
}
}GET /api/accounts # List user's accounts (Protected)
GET /api/accounts/{id} # Get account details (Protected)
POST /api/accounts # Create new account (Protected)
GET /api/accounts/{id}/balance # Get balance (Protected)GET /api/transactions # List with filters (Protected)
POST /api/transactions/deposit # Make deposit (Protected)
POST /api/transactions/withdraw # Make withdrawal (Protected)
POST /api/transactions/transfer # Transfer funds (Protected)Query Parameters for GET /api/transactions
| Parameter | Type | Description |
|---|---|---|
accountId |
UUID | Required - Account to query |
type |
string | Filter: DEPOSIT, WITHDRAWAL, TRANSFER_IN, TRANSFER_OUT |
category |
string | Filter by category |
startDate |
ISO date | Filter from date |
endDate |
ISO date | Filter to date |
page |
int | Page number (0-indexed) |
size |
int | Page size (default: 20) |
GET /api/analytics/spending-by-category # Spending breakdown (Protected)
GET /api/analytics/monthly-trend # Monthly trends (Protected)
GET /api/analytics/summary # Account summary (Protected)
GET /api/analytics/income-vs-expenses # Income comparison (Protected)Backend: Java 17, Spring Boot 3.2, Spring Security 6, Spring Cloud Gateway, Spring Data JPA, PostgreSQL 15, Redis 7, Resilience4j Frontend: React 18, TypeScript 5, Vite, TanStack Query, Material UI, Recharts DevOps: Docker Compose, GitHub Actions CI/CD, Terraform (AWS ECS/RDS/ALB), Kubernetes manifests
109 tests across 5 microservices (auth: 29, account: 23, transaction: 23, analytics: 19, gateway: 13).
cd backend && mvn test # Run all backend tests
mvn test jacoco:report # With coverage report- Connect the repository on Vercel
- Set root directory to
frontend - Set
VITE_API_URLenvironment variable to your API Gateway URL - Deploy
The microservices architecture requires multiple service instances. See render.yaml for the complete Blueprint configuration.
Required services: API Gateway, Auth, Account, Transaction, Analytics, PostgreSQL, Redis (7 total).
docker-compose up --build
# Frontend: http://localhost:3000
# API Gateway: http://localhost:8080
# Demo login: john.doe@example.com / password123- Kubernetes: Manifests in
k8s/(Deployments, Services, ConfigMap, Ingress, HPA). Seek8s/README.md. - AWS (Terraform): ECS Fargate, RDS, ALB, VPC in
terraform/. Seeterraform/README.md.
Running without Docker
Requires Java 17, Maven 3.8+, Node.js 18+, and Docker (for PostgreSQL/Redis).
# Start infrastructure
docker compose up -d postgres redis
# Build and run backend (separate terminals, start auth-service first)
cd backend && mvn clean install -DskipTests
cd backend/auth-service && mvn spring-boot:run
cd backend/account-service && mvn spring-boot:run
cd backend/transaction-service && mvn spring-boot:run
cd backend/analytics-service && mvn spring-boot:run
cd backend/api-gateway && mvn spring-boot:run
# Start frontend
cd frontend && npm install && npm run devEach service reads from application.yml with sensible defaults. Override with env vars: POSTGRES_HOST, POSTGRES_PORT, JWT_SECRET, REDIS_HOST.
Common issues
- Windows: Use
docker compose(with space), notdocker-compose - Port conflict:
docker compose downto free ports, or check withnetstat -ano | findstr :8080 - Service crash on boot: Usually PostgreSQL isn't ready yet. Check
docker compose ps, thendocker compose restart <service> - Stale data:
docker compose down -vremoves volumes, thendocker compose up --buildre-runs init-db.sql - Full reset:
docker compose down -v --rmi local && docker compose up --build
The transfer endpoint was the hardest part -- locking both accounts, updating balances, and creating two transaction records atomically. Getting the lock ordering wrong causes deadlocks, and @Lock(PESSIMISTIC_WRITE) only works if you're consistent about which account you lock first.
My initial API Gateway design re-validated JWTs by calling the auth service on every request, defeating the point of stateless tokens. Refactoring to validate signatures locally in a filter was a good lesson in what "stateless" actually means.
On the DevOps side, writing Terraform modules for AWS taught me that ~80% of infrastructure config is networking and security groups, not service deployment.
- K8s manifests tested on minikube only, not validated on EKS/GKE production clusters
- Analytics queries run against the primary database — heavy reports can impact transaction throughput
- Read replica for analytics queries to reduce primary database load
- Event sourcing for transaction history (append-only audit log)
This project is licensed under the MIT License - see the LICENSE file for details.




