A simple banking REST API for studying back-end development. It manages bank accounts and supports the core operations of creating an account, checking balances, depositing and withdrawing money — with the business rules and validations you would expect from a financial system.
The back end is built with Java 25 and Spring Boot 4, persisted in MySQL through Spring Data JPA, and served through a REST API. A small React + TypeScript front end is included to demonstrate the full stack.
- Overview
- Tech stack
- Features
- Business rules
- API endpoints
- Error handling
- Project structure
- Getting started
- Running the tests
- Database scripts
- Roadmap
- About
The Digital Banking API exposes a small set of endpoints to manage bank accounts. Each account has an auto-generated unique number, a holder name and a balance that starts at 0.00. Deposits and withdrawals update the balance, and balances can never become negative through a withdrawal.
Monetary values are handled with BigDecimal (never double), both in Java and in the database (DECIMAL(19,2)), to avoid floating-point rounding errors.
Back-End
- Java 25
- Spring Boot 4 (
spring-boot-starter-webmvc,spring-boot-starter-data-jpa,spring-boot-starter-validation) - Spring Data JPA (Hibernate)
- Bean Validation (Jakarta Validation)
- Maven (with the Maven Wrapper included)
Database
- MySQL 8
- Plain SQL scripts for schema creation and migration (Hibernate
ddl-autois set tonone)
Front-End
- React 19
- TypeScript
- Vite 8 (dev server with an
/apiproxy to the back end) - Vitest (unit tests)
- Create a bank account with a holder name and an auto-generated account number.
- List all accounts (ordered by id).
- Look up a single account by id.
- Check the balance of an account.
- Deposit into an account.
- Withdraw from an account.
- Validate operation amounts and available balance.
- Account number: randomly generated with 8 digits (zero-padded) and guaranteed unique. The service retries up to 20 times on collision; if it cannot generate a unique number, it returns
409 Conflict. - Holder name (
titular): required and limited to 120 characters. Values are trimmed before being stored. - Operation amount: must not be null, must be greater than zero, must have at most two decimal places, and must not exceed
99999999999999999.99(17 integer digits + 2 decimals). Invalid amounts return400 Bad Request. - Withdrawals: cannot exceed the available balance; otherwise the API returns
409 Conflict. - Operations on a nonexistent account: return
404 Not Found. - Initial balance: every new account starts with
0.00.
- All amounts use
BigDecimal, normalized to scale 2 withHALF_EVENrounding, and are stored asDECIMAL(19,2). - Deposits and withdrawals are executed inside a database transaction (
@Transactional) and the account row is locked for writing (PESSIMISTIC_WRITE). This prevents two concurrent operations from reading the same balance and producing an inconsistent result. - Balance updates are done with
add/subtract, and a withdrawal is rejected before any change when the balance is insufficient.
- Request validation (Bean Validation): DTOs use
@NotBlank,@Size,@NotNull,@Positiveand@Digits. Failures return400 Bad Requestwith a list of field-level details. - Domain validation (service layer): the service re-checks the amount (not null, positive, scale, upper limit) as a safety net, so the rules hold even if the API is called from another entry point.
Base URL: http://localhost:8081
| Method | Path | Purpose | Success |
|---|---|---|---|
| POST | /contas |
Create an account | 201 Created (with Location header) |
| GET | /contas |
List accounts | 200 OK |
| GET | /contas/{id} |
Get an account | 200 OK |
| GET | /contas/{id}/saldo |
Get the balance | 200 OK |
| POST | /contas/{id}/deposito |
Deposit | 200 OK |
| POST | /contas/{id}/saque |
Withdraw | 200 OK |
Create an account:
POST /contas
Content-Type: application/json
{ "titular": "Ana Silva" }{
"id": 1,
"numero": "00000042",
"titular": "Ana Silva",
"saldo": 0.00
}Deposit and withdraw:
POST /contas/1/deposito
Content-Type: application/json
{ "valor": 50.00 }POST /contas/1/saque
Content-Type: application/json
{ "valor": 20.00 }Both return the updated account:
{
"id": 1,
"numero": "00000042",
"titular": "Ana Silva",
"saldo": 30.00
}Errors are handled centrally by GlobalExceptionHandler and returned in a consistent JSON body:
{
"timestamp": "2026-01-01T12:00:00Z",
"status": 400,
"error": "Bad Request",
"message": "Dados de entrada inválidos.",
"details": ["titular: O nome do titular é obrigatório."]
}| Status | When it happens |
|---|---|
400 Bad Request |
Invalid or missing input (validation), invalid amount, or an unreadable/invalid request body |
404 Not Found |
The account id does not exist |
409 Conflict |
Withdrawal above the available balance, or it was not possible to generate a unique account number |
500 Internal Server Error |
Any unexpected error (logged on the server) |
Note: user-facing error messages are currently written in Portuguese. This is intentional so the API messages match the front end, and it is listed in the roadmap as something to revisit.
digital-banking-api/
├── src/main/java/com/digitalbanking/api/
│ ├── controller/ # REST controllers (ContaController)
│ ├── service/ # Business rules (ContaService)
│ ├── repository/ # Spring Data JPA repositories (ContaRepository)
│ ├── entity/ # JPA entities (Conta)
│ ├── dto/ # Request/response records (conta, operacao, comum)
│ ├── exception/ # Business exceptions (404, 409, 400)
│ └── handler/ # Global exception handler
├── src/main/resources/
│ ├── application.properties
│ └── db/ # schema.sql and the v1 -> v2 migration script
├── src/test/java/ # Controller and service tests
├── frontend/ # React + TypeScript + Vite application
└── docs/screenshots/ # UI screenshots used in this README
Instructions below target Windows with VS Code / PowerShell.
- JDK 25 (the project is configured for Java 25).
- MySQL 8 running on
localhost:3306. - Node.js 20 or newer (tested with Node 24) — only needed for the front end.
- Git, to clone the repository.
git clone https://github.com/evfernandes-dev/digital-banking-api.git
cd digital-banking-apiThe database is named digital_banking and is not created automatically by the application. Run the schema script for a fresh install:
Get-Content src\main\resources\db\schema.sql | mysql -u root -pPowerShell does not support the
<input redirection used in some tutorials. The command above pipes the file into the MySQL client instead. If you prefer, you can run it throughcmd:cmd /c "mysql -u root -p < src\main\resources\db\schema.sql"If the
mysqlcommand is not on yourPATH, use the full path to the MySQL client executable.
If you already have a database from the earlier version (with the clientes/transacoes tables) and want to keep the data, run src\main\resources\db\migracao-v1-para-v2.sql instead of schema.sql — see Database scripts.
The database credentials are read from environment variables (defaults: user root, empty password):
$env:DB_USERNAME = "root"
$env:DB_PASSWORD = "your_mysql_password" # leave empty if you have no passwordA .env.example file is provided for reference. Never commit real credentials.
In a terminal:
.\mvnw.cmd spring-boot:runThe API starts on http://localhost:8081.
In another terminal:
cd frontend
npm install
npm run devThe UI opens on http://localhost:5173. During development, Vite forwards requests under /api/* to http://localhost:8081, so no CORS configuration is needed on the back end.
Real screenshots of the application running against a demo database.
Home — account list
Create account
Banking operations — balance, deposit and withdrawal
Back end (JUnit 5 + MockMvc):
.\mvnw.cmd testContaControllerTest covers the HTTP layer (status codes, response body and validation) with a mocked service, and ContaServiceTest covers the business rules with a mocked repository.
Front end (Vitest):
cd frontend
npm testCurrently, the front-end tests cover the currency formatter (formatters.test.ts). The API client and the React components are not yet covered — see Roadmap.
The src/main/resources/db/ folder contains two scripts, run manually:
schema.sql— for a fresh install. Creates thedigital_bankingdatabase and thecontastable.migracao-v1-para-v2.sql— for a database created with the earlier version. The simplified version stopped using theclientesandtransacoestables and now stores the holder directly incontas.titular. This script copies each holder name from the oldclientesrelationship intocontas.titularbefore dropping the old columns and tables, so existing data is preserved.
Back up first when running the migration:
mysqldump -u root -p digital_banking > backup_digital_banking.sql
Because Hibernate runs with spring.jpa.hibernate.ddl-auto=none, the application never creates or alters the schema — all schema changes go through these scripts. Monetary columns are always DECIMAL(19,2).
Planned improvements (not implemented yet):
- Transfers between accounts.
- Transaction history.
- User authentication and authorization.
- Pagination and filtering for account listings.
- Integration tests against a real database (for example, Testcontainers).
- English (or locale-aware) API error messages.
- UI improvements and a demo deployment.
Digital Banking API is a study project for practicing back-end development and database integration. It deliberately starts from a small, working base (accounts, deposits and withdrawals) and grows over time, keeping the code organized and easy to follow.
Licensed under the MIT License.
Author: Emanuel Vitor Fernandes
- GitHub: @evfernandes-dev
- LinkedIn: Emanuel Vitor Fernandes


