A modern bank-account management platform. Built with a hexagonal-architecture backend (Java 26 + Spring Boot) and a dependencies-free vanilla JavaScript SPA frontend.
Java • Spring Boot • Spring Security • JWT • MySQL • Redis • Flyway • Vanilla JS
- Overview
- Features
- Tech Stack
- Architecture
- Getting Started
- API Reference
- Project Structure
- Testing
- Configuration
- Security & Reliability
- Roadmap
- License
Digital Wallet is a full-stack learning project by -ooloop-: a digital bank that lets users open accounts (USD, COP, EUR…), make deposits, withdrawals and peer-to-peer transfers, track every transaction, and — for admins — manage users, customers, accounts and even run financial reconciliation reports.
The emphasis is on showing how a real-world, production-minded banking backend is designed:
- a hexagonal (ports & adapters) architecture where domain logic is pure Java, unaware of Spring or the database;
- concurrent-safe money operations using transactions and pessimistic locking;
- idempotency so a double tap on "Deposit" never duplicates money;
- rate limiting backed by Redis;
- full audit logging of admin actions and money movements.
- Registration and login by username (not email).
- JWT access tokens issued and validated by the API.
- Role-based access control:
USERandADMIN. - Promote / demote administration privileges.
- Customer profile management (name, phone, ID, birth date).
- Open multiple bank accounts with balance and type (
CHECKINGS,SAVINGS). - Multi-currency balances (USD, COP, EUR…).
- Block, activate and inactivate accounts.
- Deposit, withdraw and transfer between accounts.
- Every operation is atomic (
@Transactional) and protected against concurrent races with MySQL row locks. - Transactions keep a full audit of
balanceBefore/balanceAfter. - Idempotent operations via the
Idempotency-Keyheader. - Transfer history per account with pagination.
- Admin overview dashboard with KPIs.
- User / customer / account administration.
- Audit logs: auth events, transaction events, user-control events.
- Reconciliation reports to verify financial consistency.
| Layer | Technology |
|---|---|
| Language | Java 26 |
| Framework | Spring Boot 4.1 (Web, Data JPA, Security, Validation) |
| Authentication | Spring Security + JJWT (JWT access tokens) |
| Database | MySQL 8 (via JPA/Hibernate + Flyway migrations) |
| Cache / Rate-limit | Redis |
| Migrations | Flyway |
| Frontend | Vanilla JavaScript (ES Modules), HTML5, CSS3 |
| Deployment | nginx reverse proxy + Docker |
The project follows Hexagonal Architecture (Ports & Adapters), which keeps business logic independent of the frameworks around it.
src/main/java/com/ooloop/wallet/
│
├── domain/ ← pure business logic (no Spring, no SQL)
│ ├── model/ ← records & enums (User, Customer, BankAccount, Transaction…)
│ ├── port/ ← contracts/interfaces the domain needs
│ └── event/ ← domain events (registration, money, admin)
│
├── application/ ← use cases ("orchestrators")
│ └── usecase/ ← DepositCase, WithdrawCase, TransferCase, …
│
└── infraestructure/ ← adapters: Spring, HTTP, JPA, Redis, events
├── adapter/in/web ← REST controllers
├── persistence/ ← JPA entities & repositories
├── config/ ← Spring configuration & beans
├── seed/ ← DemoDataSeeder
└── security/ ← JWT filter, security config
Any infrastructure detail (which DB, which HTTP framework, which cache) can be swapped without touching the domain or the use cases — the exact point of a hexagonal design.
A step-by-step line-by-line tutorial on locking, idempotency, rate limiting, pagination and the full deposit flow lives in
TUTORIAL_ENTENDIENDO_PROYECTO.txt.
- Java 26 (JDK) — required by
pom.xml(java.version=26). - Maven (or use the included wrapper
./mvnw). - MySQL 8 running locally.
- Redis running locally (
redis-server). - (Optional but recommended) a modern browser + any static file server for the frontend.
Configure the credentials. In development the app reads
src/main/resources/application-local.properties (this file is git-ignored
— copy from the .example and fill in your values):
cp src/main/resources/application-local.properties.example \
src/main/resources/application-local.propertiesStart MySQL and Redis, then run:
./mvnw spring-boot:runThe API is served at http://localhost:8080.
To seed demo data (recommended for a first run):
SEED_DEMO_DATA=true ./mvnw spring-boot:runThe frontend is static HTML/CSS/JS with no build step. Serve the frontend/
folder with any static server, e.g.:
python -m http.server 5500 --directory frontendThen open http://localhost:5500. In development the frontend detects the port and points the API at http://localhost:8080 (CORS is enabled for that origin).
In production the same files are served behind nginx, which also reverse-proxies
/auth, /users, /customers, /accounts and /admin to the backend on the same
origin (no CORS, no separate port).
With SEED_DEMO_DATA=true, the seeder creates:
| Username | Password | Role | Accounts |
|---|---|---|---|
admin |
Admin123! |
ADMIN | CHECKINGS USD + SAVINGS USD |
demo |
Demo123! |
USER | CHECKINGS USD + SAVINGS COP |
Login is by username (not email).
Base URL: http://localhost:8080 — all routes below /auth except public sign-in
require the Authorization: Bearer <token> header.
| Method | Path | Description |
|---|---|---|
POST |
/auth/register |
Register a new user |
POST |
/auth/login |
Login and obtain a JWT |
| Method | Path | Description |
|---|---|---|
POST |
/customers |
Register a customer |
GET |
/customers/me |
Current customer profile |
PATCH |
/customers/me |
Update own profile |
GET |
/customers |
List customers (admin) |
PATCH |
/customers/{id}/block |
Block a customer (admin) |
PATCH |
/customers/{id}/activate |
Activate a customer (admin) |
PATCH |
/customers/{id}/inactivate |
Inactivate a customer (admin) |
| Method | Path | Description |
|---|---|---|
POST |
/accounts |
Open a new account |
GET |
/accounts/me |
List my accounts |
GET |
/accounts/{accountNumber} |
Account detail |
PATCH |
/accounts/{id}/block |
Block an account (admin) |
PATCH |
/accounts/{id}/activate |
Activate an account |
PATCH |
/accounts/{id}/inactivate |
Inactivate an account |
POST |
/accounts/{accountNumber}/deposit |
Deposit funds (idempotent) |
POST |
/accounts/{accountNumber}/withdraw |
Withdraw funds (idempotent) |
POST |
/accounts/{accountNumber}/transfer |
Transfer to another account (idempotent) |
GET |
/accounts/{accountNumber}/transactions |
Account transaction history |
| Method | Path | Description |
|---|---|---|
GET |
/users |
List users (admin) |
GET |
/users/me |
Current user |
GET |
/users/{id} |
User by id (admin) |
PATCH |
/users/me |
Update own user |
PATCH |
/users/{id} |
Update a user (admin) |
PATCH |
/users/{id}/promote |
Promote to admin (admin) |
PATCH |
/users/{id}/demote |
Remove admin role (admin) |
DELETE |
/users/{id} |
Delete a user (admin) |
| Method | Path | Description |
|---|---|---|
GET |
/admin/dashboard |
Admin KPIs dashboard |
GET |
/admin/audit/auth |
Auth audit events |
GET |
/admin/audit/transactions |
Transaction audit events |
GET |
/admin/audit/user-control |
User control audit events |
POST |
/admin/reconciliation/run |
Run a reconciliation report |
GET |
/admin/reconciliation/reports |
List reconciliation reports |
Money operations accept an
Idempotency-Keyheader. The frontend generates one withcrypto.randomUUID()so a double submission cannot duplicate a transfer.
.
├── src/
│ ├── main/
│ │ ├── java/com/ooloop/wallet/ # domain, application, infraestructure
│ │ └── resources/
│ │ ├── application.properties
│ │ ├── db/migration/ # Flyway SQL migrations (V1..V9)
│ │ └── logback-spring.xml
│ └── test/ # unit & integration tests
├── frontend/
│ ├── index.html # public login + register
│ ├── app.html # authenticated shell (sidebar + router)
│ ├── css/ # reset, variables, global, components, pages
│ ├── js/ # ES modules (config, auth, api, components, pages…)
│ ├── assets/brand/ # company logo & CEO photo
│ ├── nginx.conf # reverse proxy for production
│ └── Dockerfile # frontend nginx image
├── pom.xml # Maven build (Spring Boot 4.1, Java 26)
├── docker-compose.yml # MySQL + Redis services
└── mvnw / mvnw.cmd # Maven wrapper
Run the test suite with the Maven wrapper:
./mvnw testThe suite covers core use cases (idempotency, exception handling, transfers,
locking) at the unit/application layer using spring-boot-starter-test.
All external configuration is read from environment variables with sensible
defaults (see src/main/resources/application.properties):
| Variable | Default | Description |
|---|---|---|
DB_URL |
jdbc:mysql://localhost:3306/auth_db |
JDBC connection URL |
DB_USERNAME |
— | Database user |
DB_PASSWORD |
— | Database password |
JWT_SECRET |
— | Secret used to sign JWT tokens |
JWT_EXPIRATION |
86400000 |
Token lifetime in ms (24 h) |
REDIS_HOST |
localhost |
Redis host |
REDIS_PORT |
6379 |
Redis port |
RATE_LIMIT_STORE |
redis |
Rate-limit store backend |
CORS_ALLOWED_ORIGINS |
localhost dev ports | Allowed CORS origins |
SEED_DEMO_DATA |
false |
Seed demo users/accounts on startup |
Local secrets such as
JWT_SECRET, DB password and Redis host live inapplication-local.properties, which is git-ignored and must never be committed.
- JWT authentication — stateless access tokens validated on every request;
roles are read as Spring authorities (
ROLE_USER/ROLE_ADMIN). - Password hashing — passwords are stored hashed via a
PasswordEncoderport. - Atomic money operations — deposits, withdrawals and transfers are
@Transactionaland use pessimistic locking in MySQL to prevent conflicting balance updates under concurrency. - Idempotency — unique
Idempotency-Keys prevent double charges on retries. - Rate limiting — Redis-backed limits throttle abusive requests.
- Audit logging — every auth, transaction and admin (user-control) event is logged for traceability.
- Reconciliation — scheduled/admin-driven reports cross-check financial operations for consistency.
- CORS — restricted to allowed origins; the production nginx serves frontend and API from the same origin.
- Flyway migrations — the schema is versioned and applied deterministically.
- Core banking operations (deposits, withdrawals, transfers)
- JWT authentication & role-based authorization
- Concurrency-safe locking for money operations
- Idempotency, rate limiting, audit & reconciliation
- Admin dashboard & management console
- Vanilla JS SPA frontend
- CI/CD pipeline (
TUTORIAL_CI_CD.txt) - Root
.env.example& environment-driven deployment - Full Docker orchestration (backend + frontend + MySQL + Redis)
This project is released for learning and portfolio purposes. It is a demo application and does not manage real funds. See the repository for details.
Built with care by Carlos Alberto — -ooloop- · Java, Spring & clean architecture.