BankFlow is a secure banking backend API built with Java and Spring Boot. It supports user registration, JWT authentication, account creation, safe DTO-based API responses, and transaction processing such as deposits.
The goal of this project is to practise building a production-style backend system with authentication, database persistence, transaction safety, validation, and secure banking-style business rules.
Current status: Core authentication, account creation, account listing, and deposit transaction flow are implemented. Withdraw, transfer, transaction history, and improved exception handling are in progress.
- Java 21
- Spring Boot
- Spring Security
- JWT Authentication
- Spring Data JPA
- PostgreSQL
- Maven
- Lombok
- Postman for API testing
- User registration
- BCrypt password hashing
- Login with JWT token
- Protected API endpoints using Bearer token authentication
- Create bank accounts for users
- Generate unique account numbers
- View accounts by user
- Account status support such as
ACTIVE - Account type support such as
SAVINGS - Safe response DTOs that avoid exposing sensitive user data
- Deposit money into an account
- Store transaction records
- Track transaction type using
TransactionType - Track transaction status using
TransactionStatus
- Passwords are hashed before being stored
- JWT is used to authenticate protected requests
- DTOs are used for safe API input/output
- Sensitive fields such as password are hidden from API responses
- Transaction endpoints are protected by Spring Security
BankFlow follows a layered Spring Boot architecture:
Controller Layer
→ Receives HTTP requests and returns DTO responses
Service Layer
→ Contains business logic such as account creation and transactions
Repository Layer
→ Communicates with PostgreSQL using Spring Data JPA
Entity Layer
→ Represents database tables such as User, Account, and TransactionRecord
DTO Layer
→ Defines request and response objects for safe API communication
Security Layer
→ Handles JWT generation, validation, and protected endpoint access
Represents a registered user in the system. A user can own multiple bank accounts.
Main fields:
idfullNameemailpasswordcreatedAt
The password is stored as a BCrypt hash and should never be returned in API responses.
Represents a bank account owned by a user.
Main fields:
idaccountNumberownerbalanceaccountTypeaccountStatuscreatedAt
Represents a financial transaction such as a deposit, withdrawal, or transfer.
Main fields:
idsenderAccountrecipientAccountamounttypestatusdescriptioncreatedAt
For deposits:
senderAccount = null
recipientAccount = receiving account
type = DEPOSIT
For withdrawals:
senderAccount = withdrawing account
recipientAccount = null
type = WITHDRAW
For transfers:
senderAccount = source account
recipientAccount = destination account
type = TRANSFER
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/users/register |
Register a new user |
| POST | /api/auth/login |
Login and receive JWT token |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/accounts |
Create a new bank account |
| GET | /api/accounts/users/{ownerId} |
Get all accounts for a user |
| Method | Endpoint | Description | Status |
|---|---|---|---|
| POST | /api/transactions/deposit |
Deposit money into an account | Implemented |
| POST | /api/transactions/withdraw |
Withdraw money from an account | In progress |
| POST | /api/transactions/transfer |
Transfer money between accounts | In progress |
| GET | /api/transactions/account/{accountNumber} |
View transaction history | Planned |
POST /api/users/registerRequest body:
{
"fullName": "Kian",
"email": "kian@example.com",
"password": "123456"
}Example response:
{
"id": 1,
"fullName": "Kian",
"email": "kian@example.com",
"createdAt": "2026-05-10T13:00:00"
}POST /api/auth/loginRequest body:
{
"email": "kian@example.com",
"password": "123456"
}Example response:
{
"token": "eyJhbGciOiJIUzI1NiJ9..."
}Use the returned token for protected endpoints:
Authorization: Bearer <JWT_TOKEN>POST /api/accounts
Authorization: Bearer <JWT_TOKEN>Request body:
{
"ownerId": 1,
"initialBalance": 100,
"accountType": "SAVINGS"
}Example response:
{
"id": 1,
"accountNumber": 5940369387,
"ownerId": 1,
"ownerName": "Kian",
"balance": 100,
"accountType": "SAVINGS",
"accountStatus": "ACTIVE",
"createdAt": "2026-05-10T13:00:00"
}POST /api/transactions/deposit
Authorization: Bearer <JWT_TOKEN>Request body:
{
"accountNumber": 5940369387,
"amount": 50
}Example response:
{
"id": 1,
"senderAccountNumber": null,
"recipientAccountNumber": 5940369387,
"amount": 50,
"type": "DEPOSIT",
"status": "SUCCESS",
"description": "Deposit successful",
"createdAt": "2026-05-10T13:39:42"
}git clone https://github.com/your-username/bankflow.git
cd bankflowBankFlow uses environment variables for sensitive configuration.
Example:
export DATABASE_URL="jdbc:postgresql://localhost:5432/bankflow"
export DATABASE_USERNAME="your_username"
export DATABASE_PASSWORD="your_password"
export JWT_SECRET="your-secret-key-must-be-at-least-32-characters"
export JWT_EXPIRATION="86400000"spring.application.name=bankflow
spring.datasource.url=${DATABASE_URL}
spring.datasource.username=${DATABASE_USERNAME}
spring.datasource.password=${DATABASE_PASSWORD}
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
jwt.secret=${JWT_SECRET}
jwt.expiration=${JWT_EXPIRATION:86400000}./mvnw spring-boot:runThe API will run at:
http://localhost:8080
| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL JDBC database URL |
DATABASE_USERNAME |
Database username |
DATABASE_PASSWORD |
Database password |
JWT_SECRET |
Secret key used to sign JWT tokens |
JWT_EXPIRATION |
JWT expiration time in milliseconds |
- Passwords are hashed using BCrypt.
- JWT tokens are used to protect private endpoints.
- Sensitive fields are hidden from API responses using DTOs.
- The API uses
Authorization: Bearer <JWT_TOKEN>for protected routes. - Real secrets should not be committed to GitHub.
- User registration
- BCrypt password hashing
- JWT login
- JWT-protected endpoints
- Account creation
- Account response DTO
- Account listing by user
- Deposit transaction
- Transaction response DTO
- Withdraw transaction
- Transfer transaction
- Transaction history
- Custom exception handling
- Better error response format
- Use JWT identity instead of sending
ownerIdfrom the client - Add withdraw and transfer transaction flows
- Add transaction history endpoint
- Add global exception handling with custom error responses
- Add account ownership validation
- Add admin role and account freeze/unfreeze functionality
- Add audit logs for important actions
- Add idempotency keys to prevent duplicate transaction processing
- Add Swagger/OpenAPI documentation
- Add Docker and Docker Compose support
- Add unit and integration tests
- Add CI/CD with GitHub Actions
- Deploy the backend to a cloud platform
- Explore microservices architecture with API Gateway, Auth Service, Account Service, Transaction Service, and Notification Service
While building BankFlow, I practised:
- Designing layered backend architecture with Spring Boot
- Implementing JWT authentication with Spring Security
- Hashing passwords using BCrypt
- Modelling entity relationships using JPA
- Using DTOs to separate API responses from database entities
- Handling financial values with
BigDecimal - Creating transaction records for banking operations
- Protecting endpoints with JWT filters
- Testing APIs with Postman
- Working with PostgreSQL and Spring Data JPA