A production-style banking backend built with FastAPI, PostgreSQL, SQLAlchemy, and Redis, focused on the engineering problems that make financial systems reliable rather than treating banking as a CRUD exercise.
Engineering focus: financial correctness, concurrency control, idempotency, rate limiting, authentication, auditability, and failure-safe transaction processing.
Client
β
β POST /transactions
β Authorization + Idempotency-Key
βΌ
FastAPI API Layer
β
βββ Authenticate request
βββ Check Redis idempotency record
β βββ existing result β return cached response
β
βΌ
Transaction Service
β
βββ Validate amount
βββ Validate sender / receiver
βββ Validate account state
βββ Check sufficient balance
βββ Reject self-transfer
β
βββ PostgreSQL row-level locking
β SELECT ... FOR UPDATE
β
βββ Debit sender
βββ Credit receiver
βββ Create transaction record
βββ Create debit ledger entry
βββ Create credit ledger entry
βββ Create audit record
β
βββ COMMIT
β
βββ Cache successful response in Redis
The project is deliberately presented from the hardest engineering concerns down to the supporting backend functionality.
A bank account is shared mutable state. Concurrent requests must not be allowed to update the same balance using stale state.
The transfer workflow uses PostgreSQL row-level locks with:
SELECT ... FOR UPDATEThe sender and receiver account rows are selected in a deterministic order and locked before balance mutation.
Goal:
Concurrent transfer requests
β
PostgreSQL row locks
β
Conflicting updates are serialized
β
No stale balance update / double-spend
A transfer is treated as one database transaction.
These operations are coordinated together:
Sender balance update
+
Receiver balance update
+
Transaction record
+
Debit ledger entry
+
Credit ledger entry
+
Audit record
The service explicitly commits the operation only after the workflow succeeds. Expected application failures and unexpected exceptions trigger a database rollback.
Failure
β
ROLLBACK
β
No partially committed transfer state
For a successful transfer, the system creates two ledger entries:
Sender account β DEBIT
Receiver account β CREDIT
The current balance answers what the account contains now; the ledger provides the transaction trail behind that state.
The repository also contains reconciliation logic intended to compare total debit and credit amounts.
Transaction requests require an idempotency key.
First request
β
Process transfer
β
Commit
β
Store successful response in Redis
Retry with same key
β
Redis lookup
β
Return previously stored response
This protects the transaction endpoint from duplicate processing caused by client retries, timeouts, or repeated submissions using the same key.
The project implements a Redis-backed sliding-window limiter.
The rate-limit state contains:
previous_count
current_count
window_start
The implementation uses Redis optimistic transactions:
WATCH
β
Read current state
β
Calculate effective request count
β
MULTI
β
Write new state
β
EXEC
The current implementation uses a 60-second window and a configured request limit. It is applied to login requests and the repository also contains reusable user-scoped dependency support for endpoint rate limiting.
When the effective limit is exceeded:
429 Too Many RequestsImplemented authentication features:
- JWT access tokens
- JWT refresh tokens
- explicit access/refresh token type validation
- token expiration
- protected endpoints through FastAPI dependencies
- bcrypt password hashing
The access token carries the authenticated user identifier and token type, and the backend verifies both the JWT signature and token type before loading the user.
Refresh tokens are persisted as database sessions containing:
user_id
refresh_token
device_name
created_at
expires_at
The login flow also captures the request User-Agent, allowing the session to retain device information.
The refresh flow is:
Verify JWT refresh token
β
Check Redis blacklist
β
Find stored refresh session
β
Create new access token
β
Create new refresh token
β
Blacklist old token
β
Replace stored session token
β
Commit
Logout performs:
Verify refresh token
β
Check whether token is already invalidated
β
Blacklist token in Redis
β
Delete refresh session from PostgreSQL
β
Commit
Redis acts as a fast lookup layer for invalidated refresh tokens.
Registration supports email verification using short-lived Redis tokens:
Register
β
Generate verification token
β
Store token in Redis with TTL
β
Send verification email through SMTP
β
User opens verification link
β
Mark email as verified
β
Delete token
The verification-token TTL is enforced by Redis.
Password recovery follows a similar short-lived-token workflow:
Forgot password
β
Generate reset token
β
Store token in Redis
β
Send reset link
β
Validate token
β
Hash new password
β
Update PostgreSQL
β
Delete reset token
Implemented capabilities include:
- user registration
- email validation
- email uniqueness checks
- phone-number uniqueness checks
- bcrypt password hashing
- email verification state
- lookup by user ID
- lookup by email
- lookup by phone
- password reset
Authenticated users can create accounts.
The account model contains:
Account ID
Account Number
Owner User ID
Account Type
Status
Balance
Created At
Account numbers are generated using a PostgreSQL sequence-backed identifier plus configured prefixes.
Before modifying financial state, the transfer service validates:
- amount must be positive
- sender account must exist
- receiver account must exist
- sender account must not be frozen
- receiver account must not be frozen
- sender must have sufficient balance
- sender and receiver cannot be the same account
Each transfer creates a transaction record containing:
Transaction ID
Amount
Transaction Reference
Sender Account
Receiver Account
Status
Description
Created At
Transaction references are generated from a PostgreSQL sequence-backed identifier and application configuration.
Authenticated users can retrieve their transaction history with:
- transaction reference
- amount
- sender account number
- receiver account number
- transaction status
- description
- creation timestamp
Financial operations create an audit record containing:
User
Transaction
Action
Description
Timestamp
The audit record is created as part of the same database transaction as the transfer.
API / Controllers
β
Service Layer
β
Repository Layer
β
PostgreSQL / Redis
Responsible for:
- HTTP routes
- request/response schemas
- dependency injection
- authentication dependencies
- rate-limit integration
Responsible for:
- business rules
- authentication workflows
- fund-transfer orchestration
- commit / rollback boundaries
- coordinating database and Redis operations
Responsible for:
- database queries
- inserts and updates
- row locking
- Redis persistence
- refresh-session persistence
- audit persistence
users
β
ββββββββββΊ accounts
β β
β ββββββββββΊ transactions
β β
β ββββββββββΊ ledger
β ββββββββββΊ audit_log
β
ββββββββββΊ refresh_sessions
| Entity | Responsibility |
|---|---|
users |
Identity, credentials, verification state |
accounts |
Ownership, type, status, current balance |
transactions |
Transfer-level financial records |
ledger |
Debit / credit accounting entries |
Audit_log |
Financial activity traceability |
refresh_sessions |
Refresh-token session state |
Redis supports several independent pieces of application state:
| Redis capability | Purpose |
|---|---|
| Idempotency | Cache a successful transaction response by idempotency key |
| Rate limiting | Maintain sliding-window request state |
| Refresh blacklist | Reject invalidated refresh tokens quickly |
| Email verification | Store short-lived verification tokens |
| Password reset | Store short-lived password-reset tokens |
This keeps frequently accessed, short-lived state in Redis while PostgreSQL remains the primary source of persistent banking state.
Validation / business-rule failure
β
db.rollback()
β
HTTP error
Unexpected exception
β
db.rollback()
β
HTTP 500 response
β
No partial financial state committed
This is particularly important for fund transfers because a transfer updates multiple related records.
The repository contains focused checks around the Redis and rate-limiting components, including:
- rate-limit behavior
- Redis optimistic transaction behavior using
WATCH - transaction-history test scaffolding
The Redis test demonstrates the conflict path where another client changes a watched key before EXEC, producing a Redis watch conflict.
POST /register
POST /login
POST /refresh
POST /logout
POST /forgot_password
GET /verify-email
POST /reset-passwordPOST /create_accountPOST /transactions
GET /transactions_history- Python
- FastAPI
- Uvicorn
- PostgreSQL
- SQLAlchemy
- Redis
- JWT
- bcrypt password hashing
- SMTP
- Pydantic
- Git
- GitHub
Banking-API/
β
βββ app/
β βββ api/
β β βββ AccountCreate.py
β β βββ Transactions.py
β β βββ User_login.py
β β βββ User_register.py
β β
β βββ core/
β β βββ config.py
β β βββ redis_client.py
β β βββ security.py
β β
β βββ db/
β β
β βββ dependencies/
β β βββ rate_limit.py
β β
β βββ models/
β β βββ accounts.py
β β βββ audit.py
β β βββ ledger.py
β β βββ refresh_sessions.py
β β βββ transactions.py
β β βββ user.py
β β
β βββ repositries/
β β βββ Transaction_repositries.py
β β βββ account_repositrie.py
β β βββ audit_repositry.py
β β βββ login_repositry.py
β β βββ redis_ratelimiter_service.py
β β βββ redis_repositries.py
β β βββ user_repositries.py
β β
β βββ schemas/
β β
β βββ services/
β β βββ AccountCreation.py
β β βββ Transaction_services.py
β β βββ auth_services.py
β β βββ email_service.py
β β βββ hashing.py
β β βββ login_service.py
β β βββ rate_limiter_service.py
β β
β βββ tests/
β βββ test_rate_limiter.py
β βββ test_redis_transactions.py
β βββ test_transaction_history.py
β
βββ requirements.txt
git clone https://github.com/GenZ-CODER-X/Banking-API.git
cd Banking-APIpython -m venv venv
source venv/bin/activateWindows:
venv\Scripts\activatepip install -r requirements.txtCreate a .env file matching the settings expected by app/core/config.py.
DATABASE_URL=<postgresql-connection-string>
SECRET_KEY=<strong-secret>
ACCESS_TOKEN_EXPIRE_MINUTES=<minutes>
REFRESH_TOKEN_EXPIRE_MINUTES=<minutes>
ALGORITHM=<jwt-algorithm>
SECRET_PREFIX_STR=<prefix>
SECRET_PREFIX_INT=<integer>
SECRET_PREFIX_TRANSACTION=<transaction-prefix>
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DB=0
SMTP_HOST=<smtp-host>
SMTP_PORT=<smtp-port>
SMTP_EMAIL=<sender-email>
SMTP_PASSWORD=<smtp-password>
BACKEND_URL=http://localhost:8000uvicorn app.main:app --reloadBecause a balance is shared mutable state.
Without locking, two concurrent requests can read the same balance before either update becomes visible to the other request.
With row-level locking:
Request A βββΊ lock account row βββΊ update βββΊ commit
Request B βββΊ waits for lock ββββββΊ read latest state
The database therefore participates directly in maintaining financial consistency.
Clients can retry after a timeout even when the original transaction succeeded.
The idempotency key lets the backend distinguish:
New payment
from:
Retry of an already processed request
The balance represents current state.
The ledger represents the accounting trail behind that state.
Together they provide both:
Current balance
+
Historical financial activity
The current repository is the banking core. The following are planned extensions and are not claimed as implemented here.
- exponential backoff and retry handling
- payment state machine
- provider failure recovery
- webhooks
- reconciliation
- transaction events
- outbox pattern
- Kafka / event streaming
- asynchronous downstream consumers
- real-time fraud scoring
- behavioral and temporal features
- IP / device / session context
- ML-based transaction risk scoring
- rule + model decisioning
Banking API
β
Reliable Payment Infrastructure
β
Event-Driven Payments
β
Real-Time Fraud Intelligence
This project treats banking primarily as a correctness and reliability problem rather than a collection of CRUD endpoints.
Authentication
+
Concurrency
+
ACID Transactions
+
Idempotency
+
Rate Limiting
+
Ledger Design
+
Auditability
+
Redis State Management
+
Failure Handling
=
Reliable Banking Backend
Repository: https://github.com/GenZ-CODER-X/Banking-API
