-
Technology Stack:
- FastAPI (Python web framework)
- PostgreSQL database
- JWT authentication
- Python 3.12
-
Database Schema:
userstable: Stores user accounts with username, password hash, and balancetransactionstable: Records all monetary operations (deposits, withdrawals, transfers)
-
API Endpoints:
/api/login: Authentication endpoint/api/balance: Get user's current balance/api/transactions: Get transaction history/api/transfer: Transfer funds between users
The application provides two different frontend implementations:
-
SPA (Single Page Application):
- Built with React + Vite + Material UI
- Served at
http://localhost:8000/spa - Uses JWT tokens for authentication
- Client-side routing with React Router
-
SSR (Server-Side Rendered):
- Built with Jinja2 templates + HTMX
- Served at
http://localhost:8000/ssr - Uses server-side sessions (cookie-based)
- Better for SEO and works without JavaScript
The application includes a complete monitoring stack:
- Prometheus for metrics
- Grafana for dashboards
- OpenTelemetry for tracing
- Tempo for distributed tracing
- All orchestrated with Docker Compose
-
Backend Setup:
- Create Python virtual environment
- Set up PostgreSQL database
- Install dependencies from requirements.txt
- Run with
uvicorn main:app --reload
-
Frontend Setup:
- Navigate to frontend directory
- Install dependencies with
npm install - Run development server with
npm run dev
- Automated tests can be run with
pytest - Code quality tools:
- Backend:
ruff check --fixandruff format - Frontend: ESLint and Prettier
- Backend:
- Password hashing with bcrypt
- JWT-based authentication for API
- Session-based authentication for SSR
- Protected routes and endpoints
- Input validation with Pydantic
This is a well-structured application that demonstrates modern web development practices, including:
- Multiple frontend approaches (SPA and SSR)
- Comprehensive monitoring
- Security best practices
- Clean code organization
- Automated testing
- Docker-based deployment
Would you like me to elaborate on any particular aspect of the application?
-
put .env file to the root directory
-
python venv creation (python3.12 used): python3 -m venv venv source venv/bin/activate pip install -r requirements.txt pip install --upgrade pip
-
create the db: psql postgres CREATE USER bankadmin WITH PASSWORD 'password'; CREATE DATABASE bankapp OWNER bankadmin; GRANT ALL PRIVILEGES ON DATABASE bankapp TO bankadmin;
-
create schema from schema.sql: psql -U bankadmin -d bankapp -f schema.sql
-
create test user: use utils/get_password_hash.py to generate a password hash for "test_user" with "password_test_user"
psql -U bankadmin -d bankapp
INSERT INTO users (username, password_hash, balance) VALUES ('test_user', '$2b$12$ermJY38l7dGmYBuUcJ3/qutW6NouRvjOR2GmZU5HABh6s6JxPevBm', 1000.00);
-
run the app from backend dir: uvicorn main:app --reload
-
test /api/login endpoint through Swagger UI: Request body: { "username": "test_user", "password": "password_test_user" } Response example: { "access_token": "xyz", "token_type": "bearer" }
7-8. go to Authorize and input the token. Then use TryItOut/Execute for /api/balance and /api/transactions endpoints.
- test api/transfer endpoint: create test user 2: use utils/get_password_hash.py to generate a password hash for "test_user2" with "password_test_user2"
INSERT INTO users (username, password_hash, balance) VALUES ('test_user2', '$2b$12$ff0HQcrKzv7rxhX.n4gXue7OoSZ15pTAJr5HGIFuGX94bq3TiBFly', 1000.00);
open in 2 browsers and try to transfer money between 2 users
-
run automated tests through "PYTHONPATH=. pytest tests/"
-
in case any code modifications are done, use "ruff check --fix" and "ruff format".
PostgreSQL schema is designed to reflect a simplified banking system while remaining extensible and normalized:
-
users- Stores registered users.
- Fields:
id,username(unique),password_hash,balance,is_active,created_at balanceis stored here for fast access, though all changes are also recorded intransactions.
-
transactions- Represents all monetary operations: deposits, withdrawals, transfers.
- Fields:
id,user_id,amount,type(deposit,withdrawal,transfer_in,transfer_out),created_at - All transactions are immutable and auditable.
-
Why no separate
accountstable?- Each user has one account by default for simplicity. Extending to multiple accounts per user would require another table and minor refactoring.
- Uses JWT tokens for sessionless, stateless security.
- Login issues a token with limited expiry.
- Protected endpoints require an
Authorization: Bearer <token>header.
-
POST /api/login- Accepts username/password, returns JWT.
-
GET /api/balance- Returns current user balance from
userstable.
- Returns current user balance from
-
GET /api/transactions?page&limit- Returns paginated transaction history from
transactions.
- Returns paginated transaction history from
-
POST /api/transfer- Transfers funds from the authenticated user to another registered user.
- Performs balance checks and records both
transfer_outandtransfer_intransactions atomically.
- Uses Pydantic schemas to validate requests/responses.
- Returns appropriate HTTP status codes:
401 Unauthorizedfor invalid tokens422 Unprocessable Entityfor validation errors400 Bad Requestor403 Forbiddenfor business logic errors (e.g. insufficient funds)
- Simplicity first: easy to reason about, modify, and extend.
- Transactional integrity: all operations that mutate data are wrapped in database transactions using SQLAlchemy's session (
db.commit()/db.rollback()) to ensure consistency. - Security: hashed passwords with
bcrypt, JWT auth, limited error leakage. - Extensibility: clean separation of models, routes, and schemas allows for adding features like admin views, currency support, or multi-account management.
This project provides two separate frontends to the same backend API:
- A Single-Page Application (SPA) built with React + Vite + Material UI
- A Server-Side Rendered (SSR) frontend built with Jinja2 templates + HTMX
Both versions maintain feature parity across login, dashboard, transaction history, and fund transfer.
- Served under:
http://localhost:8000/spa - Built with React Router,
BrowserRouter basename="/spa" - Authentication via JWT tokens, stored in-memory (React context)
- Communicates with
/api/*endpoints via Axios - Token is required in the
Authorizationheader for all protected API calls - Built with Material UI
- Served under:
http://localhost:8000/ssr - Uses Jinja2 templates rendered by FastAPI for each page
- Authentication uses server-side sessions (cookie-based)
- Uses HTMX for partial updates (e.g., refresh balance, transactions)
- All routing and rendering are handled on the server
- Safer for environments where JavaScript may be restricted or disabled
| Feature | SPA (React) | SSR (HTMX) |
|---|---|---|
| Routing | Client-side (React Router) | Server-side (FastAPI routes) |
| Auth Storage | JWT (in React context) | Session cookie |
| Communication | Axios to /api/* |
HTML forms + HTMX calls to /ssr/* |
| Performance | Fast UX once loaded | Faster first page load |
| SEO-Friendly | β (unless prerendered) | β Yes |
| JS Dependency | Required | Minimal (HTMX only) |
| State Management | React useState / Context | Session + database |
| Best Use | Modern apps, dashboards | Progressive enhancement, basic devices |
This project supports both a React-based SPA and an SSR (server-side rendered) HTMX frontend. Both access the same API backend.
-
Navigate to the
frontend/directory:cd frontend -
Install dependencies:
npm install
-
Run development server:
npm run dev
Then open: http://localhost:5173
-
Or to build for production:
npm run build
The output will be in
frontend/dist/
- β
/spa/login: Login withtest_user/password_test_user - β
/spa/balance: Check balance - β
/spa/transactions: See transaction history - β
/spa/transfer: Transfer funds to another user - β Invalid login β shows toast error
- β Logout β returns to login page
- π Refresh
/spa/*pages β should not 404 - π Token-based authentication in memory
-
No separate build is needed β served directly via FastAPI
-
Start backend:
cd backend uvicorn main:app --reload -
Open in browser:
http://localhost:8000/ssr/login
- β
/ssr/login: Login with valid credentials - β Invalid login β shows inline error
- β
/ssr/balance: Displays server-rendered balance - β
/ssr/transactions: Lists recent transactions - β
/ssr/transfer: Transfer funds with form - β Transfer to self or invalid β shows error inline
- β Session-based auth using cookies
- β
Logout β redirects to
/ssr/login
- β Logging in via SSR does not affect SPA
- β SPA uses JWT in headers; SSR uses cookies and sessions
-
Run backend in dev mode:
cd backend uvicorn main:app --reload -
Run test suite from root:
PYTHONPATH=. pytest tests/
For frontend (SPA):
cd frontend
npm run lint # check
npm run format # check format
npm run format:fix # fix formattingFor backend (SSR + API):
cd backend
ruff check --fix
ruff format- Docker & Docker Compose
.envfiles for backend and frontend- Node.js (for local frontend development, optional)
The stack is orchestrated with docker-compose. It includes:
- PostgreSQL for database
- FastAPI backend (
/backend) - Frontend served with
npm run preview - Prometheus for metrics scraping
- Grafana for dashboarding
- OpenTelemetry Collector for ingest/export
- Tempo for distributed tracing
-
Build and launch all services:
docker compose up --build
-
Service Ports:
- Backend API: http://localhost:8000
- Frontend: http://localhost:4173
- Grafana: http://localhost:3000
- Prometheus: http://localhost:9090
Configured in monitoring/otel-collector-config.yaml:
- Receives metrics & traces via OTLP (HTTP/gRPC)
- Exports:
- Metrics to Prometheus on port
8889 - Traces to Tempo on port
4317
- Metrics to Prometheus on port
Configuration file: monitoring/prometheus.yml
- Scrapes:
- Itself at
localhost:9090 - OpenTelemetry Collector at
otel-collector:8889
- Itself at
Tempo is configured to receive OTLP traces over gRPC and HTTP.
Configuration file: monitoring/tempo.yaml
- Trace storage is local:
/tmp/tempo/traces
Provisioned with:
- Data Sources (
monitoring/grafana/provisioning/datasources.yml)- Prometheus
- Tempo
- Dashboards (
monitoring/grafana/provisioning/dashbooards.yaml)- Includes dashboard from
app-metrics-dashboard.json
- Includes dashboard from
Default login: admin / admin
main.py includes:
opentelemetrysetup withOTLPMetricExporterandOTLPSpanExporter- Tracing and metrics collection with FastAPI & requests instrumentation
- Middleware to count HTTP requests
Metrics collected:
- HTTP request count
- Traces with span context
- Dashboard is auto-refreshed every 10s (
app-metrics-dashboard.json) - To reset Grafana or Prometheus, delete volumes:
postgres_data,grafana_data - Logs and traces are available in Docker logs or Grafana Explore tab
This is the frontend of a simple banking application built with:
cd frontend
npm installnpm run devThis starts Vite on http://localhost:5173 (default).
npm run lintnpm run formatfrontend/
βββ public/ # Static assets
βββ src/
β βββ pages/ # Page components (Login, Balance, etc.)
β βββ context/ # Auth context (with useAuth hook)
β βββ services/axios.js # Axios instance
β βββ utils/ # Shared utilities (e.g. handleApiError)
β βββ main.jsx # App entry point
βββ .eslintrc.cjs # ESLint config
βββ .prettierrc # Prettier config
βββ vite.config.js # Vite config
βββ package.json
- Secure login with token auth
- Protected routes
- Styled with Material UI
- Toast-based error/success handling
- Lint + format setup for clean code
npm run buildOutputs a static site to the dist/ folder.
To wipe dependencies and rebuild:
rm -rf node_modules dist package-lock.json
npm install