A production-style full-stack application to manage personal loans and forecast monthly EMI obligations with a year-based calendar view.
People often manage multiple liabilities (bank loans, personal loans, credit-card EMIs, BNPL) using spreadsheets or rough calculators.
This project solves that by providing a focused EMI visibility platform where users can:
- manage multiple loans
- see total EMI burden month-by-month
- understand exactly which loans contribute to a selected month
- plan future obligations using a 12-month calendar
This project intentionally focuses on forecasting and visibility from user-provided loan data, not lender-grade amortization calculations.
- Clean layered backend architecture (
controller -> service -> repository) - Stateless JWT authentication with Spring Security
- Strong validation + centralized exception handling
- Query/data integrity optimizations (indexes + unique constraints)
- Redis-backed caching using Spring Cache (
@Cacheable,@CacheEvict) - Dockerized runtime stack (App + MySQL + Redis)
- Integration tests with isolated test profile (H2 + cache disabled)
- User registration and login (phone + password)
- JWT-secured APIs
- Loan lifecycle management:
- create
- update
- delete
- close early
- User-scoped access control for loan data
- Yearly EMI calendar (
12month summary) - Month-wise loan contribution breakdown
- Duplicate loan prevention per user
- EMI forecasting based on user-entered loan data
- 12-month EMI visibility
- loan contribution tracing per month
- amortization schedules
- interest vs principal breakdown
- bank API integrations
- payment reminders/tracking
- credit score or budgeting modules
- Java 17
- Spring Boot 3.5.11
- Spring Web, Spring Data JPA, Spring Security
- Bean Validation (
jakarta.validation) - JWT (
jjwt) - Redis (
spring-boot-starter-cache,spring-boot-starter-data-redis) - MySQL
- HTML5 + CSS3
- Vanilla JavaScript (modular ES modules)
- Maven Wrapper
- H2 (test profile)
- Docker + Docker Compose
controller: request/response handlingservice: business logic + authorization checksrepository: persistence access via Spring Data JPAdto: input/output contractsentity: DB mapping and constraints
- Stateless security model (
SessionCreationPolicy.STATELESS) - JWT validated in
JwtAuthenticationFilter - Authenticated user UUID stored in security context
- BCrypt password hashing
- Request validation with annotations like
@NotBlank,@NotNull,@Positive,@Size - Service-level business rule enforcement (ownership checks, close-date rules)
- Centralized exception mapping in
GlobalExceptionHandler - Consistent API error payload with timestamp, status, code, message, path
users.phone_numberindexloans.user_idindex- Composite unique constraint on loans:
(user_id, loan_name, provider_name, start_date)
- User-scoped queries and pagination to keep reads efficient
- Structured service-level logs on:
- request start
- success path
- warning/failure path
- Contextual identifiers included (
userId,loanId) for easier debugging
The frontend is intentionally framework-free, lightweight, and modular.
api.js: centralized request helper, token attachment, error normalizationauth.js: login/register flow, auth guards, token lifecycleloans.js: loan CRUD flows, detail screen, modal workflowscalendar.js: year navigation and monthly breakdown renderingui.js: alerts, modals, navigation behaviorutils.js: formatting/sanitization utilities
UI characteristics:
- responsive layout for desktop/mobile
- modal-driven interactions for speed
- clear empty/error/success states
Caching is implemented with Spring Cache abstraction and Redis as the backend.
- Year calendar
- Month breakdown
- Loan by ID
- Paginated user loans
- Current user profile
On loan mutations (create, update, delete, close), relevant cache regions are evicted to avoid stale forecast/list/detail views.
@EnableCachingat app boot- Custom Redis cache manager
- JSON serialization for cache values
- Per-cache TTL tuning
- Test profile disables cache (
spring.cache.type=none) for deterministic tests
The app is packaged with Docker Compose services:
app- Spring Boot servicedb- MySQL 8.0redis- Redis 7
Containerization decisions:
- isolated bridge network
- service health checks (MySQL + Redis)
- startup dependency ordering
- persistent DB volume (
db_data) - env-based runtime configuration
POST /api/auth/registerPOST /api/auth/login
GET /api/users/me
POST /api/loansGET /api/loansGET /api/loans/{loanId}PUT /api/loans/{loanId}DELETE /api/loans/{loanId}PATCH /api/loans/{loanId}/close
GET /api/calendar/{year}GET /api/calendar/{year}/{month}
docker compose up --buildThen open http://localhost:8080.
Set required env/properties:
SPRING_DATASOURCE_URLSPRING_DATASOURCE_USERNAMESPRING_DATASOURCE_PASSWORDSPRING_DATA_REDIS_HOSTSPRING_DATA_REDIS_PORTJWT_SECRET
Run:
./mvnw spring-boot:runRun:
./mvnw testTest profile uses:
- H2 in-memory database
- cache disabled
This keeps tests isolated and fast.
src/main/java/com/emiplanner
config/
controller/
dto/
entity/
exception/
repository/
security/
service/
src/main/resources/static
*.html
css/styles.css
js/*.js
- refresh-token flow and token revocation
- fine-grained user-key cache eviction (instead of broader region eviction)
- OpenAPI/Swagger documentation
- CI pipeline and deployment workflow
- observability (metrics/tracing)






