Customers, accounts and transactions served over a documented REST API, with MySQL triggers, stored procedures and audit logs doing the work that application code usually duplicates.
Admin UI: customers, a customer's accounts, an account's transactions. Then live API requests against the same data, including the audit log written by triggers.
Screenshots of every section
Admin UI β customers β full CRUD over the customer table
Admin UI β accounts of a customer β filtered by customer id
Admin UI β transactions of an account
Admin UI β transactions across several accounts β many-to-many link table
API β all endpoints β 30+ operations grouped by resource, JWT-protected ones marked with a lock
API β GET /customers β live request and response
API β GET /transactions
API β GET /logs β the audit trail written by database triggers
- Docker and Docker Compose, or Python 3.12+ with a reachable MySQL 8 instance
git clone https://github.com/sergiyclas/banking-api-platform.git
cd banking-api-platform
cp .env.example .env # set DB_PASSWORD and JWT_SECRET
docker compose upThe compose file starts MySQL, waits for it to become healthy, then starts the API.
git clone https://github.com/sergiyclas/banking-api-platform.git
cd banking-api-platform
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # point DB_* at your MySQL instance
python app.pyOpen http://localhost:5000/apidocs for the API documentation.
To launch the admin UI in a separate process:
python ui.py # http://localhost:7860On the first start the app creates the database and runs
scenary.sql, which builds the schema and seeds sample customers, accounts and transactions.
- Triggers write the audit log on every insert, update and delete
- Stored procedures encapsulate multi-step operations, called by name from the DAO
- SQL functions back the aggregate endpoints such as
POST /transactions/count - The API stays thin instead of reimplementing the same guarantees in Python
- 30+ operations across customers, accounts, transactions and logs
- Every endpoint carries an OpenAPI description, so
/apidocsis a usable client - Request and response schemas visible per endpoint
POST /loginissues a token; account endpoints require it- Swagger accepts the token through the standard Authorize dialog
- Signing key comes from the environment, never from the code
- Gradio interface for CRUD, filtering and demos
- Calls the very same service classes the HTTP layer uses β no second implementation
- Runs as a separate process, so the API can be deployed without it
infra/terraformprovisions a VPC across two availability zones- Application load balancer in front of an auto-scaling group
terraform.tfvars.exampledocuments every input variable
load_test/drives concurrent traffic against every endpoint- Reports latency and error rate per endpoint
- Packaged in its own container so it can run against a deployed instance
flowchart LR
C([Client]) -->|REST| API[Flask API]
G([Admin]) -->|Gradio UI| SVC
API --> SVC[Service layer]
SVC --> DAO[DAO layer]
DAO --> DB[(MySQL 8)]
DB -->|triggers| LOG[(Audit log)]
Layers
| Layer | Responsibility | Location |
|---|---|---|
| Route | HTTP handling, OpenAPI docs, JWT checks | my_project/auth/route/ |
| Service | Business operations | my_project/auth/service/ |
| DAO | SQL, lazily opened connections | my_project/auth/dao/ |
| Domain | Plain data classes | my_project/auth/domain/ |
| Database | Schema, triggers, procedures, functions | scenary.sql, triggers_procedures.sql |
Project layout
my_project/ route β service β DAO β domain layers
config/ database configuration from environment variables
infra/terraform/ AWS VPC, ALB and auto-scaling group
load_test/ concurrent traffic generator (containerised)
tests/ unit tests
scenary.sql schema and seed data
triggers_procedures.sql triggers, stored procedures and functions
ui.py Gradio admin interface
Selected endpoints; the full list with request and response schemas is on /apidocs.
| Method | Endpoint | Description |
|---|---|---|
POST |
/login |
Obtain a JWT token |
GET |
/customers |
List customers |
POST |
/customers |
Create a customer |
GET |
/customers/{id}/accounts |
Accounts of one customer |
GET |
/accounts |
List accounts (JWT required) |
GET |
/transactions |
List transactions |
POST |
/transactions/count |
Aggregate transactions through a SQL function |
GET |
/accounts/{id}/transactions |
Transactions linked to an account |
GET |
/logs |
Audit log written by triggers |
GET |
/health |
Health probe |
Example
curl http://localhost:5000/customers[
{ "customer_id": 1, "customer_name": "Customer 1",
"email": "customer1@mail.com", "phone": "+380981234567" }
]| Variable | Description | Default |
|---|---|---|
DB_HOST |
MySQL host | localhost |
DB_PORT |
MySQL port | 3306 |
DB_USER |
MySQL user | root |
DB_PASSWORD |
MySQL password | β |
DB_NAME |
Database name | online_banking |
JWT_SECRET |
Signing key for JWT tokens | β |
HOST / PORT |
Flask bind address | 0.0.0.0 / 5000 |
cd infra/terraform
cp terraform.tfvars.example terraform.tfvars # set region, project name, CIDRs
terraform init
terraform plan
terraform applyTerraform creates a VPC with public subnets in two availability zones, an internet gateway, an application load balancer and an auto-scaling group running the container.
.github/workflows/deploy.yml ships the app to a VM over SSH. It is triggered manually
(workflow_dispatch) because it depends on repository secrets for the target host.
pip install pytest ruff
pytest
ruff check .Unit tests cover the database configuration and the DAO connection handling, and run without a database. Load testing is separate:
cd load_test
docker build -t banking-load-test .
docker run --rm banking-load-testSerhiy Dzen β AI Software Engineer
Licensed under the MIT License








