- A minimal FastAPI + PostgreSQL bank backend supporting user auth, account lifecycle, deposits, withdrawals, transfers, and transaction history. Authentication uses HTTP Basic (username/password) checked per request to scope account access.
- User registration and authentication using HTTP Basic Auth
- Multiple accounts per user
- Deposit and withdrawal operations
- Inter-account transfers with balance validation
- Transaction history retrieval
- Complete authorization and ownership checks
- FastAPI: Modern, fast web framework for building APIs
- asyncpg: High-performance PostgreSQL database driver
- PostgreSQL: Reliable ACID-compliant relational database
- Pydantic: Data validation using Python type annotations
- bcrypt: Secure password hashing
- pytest + httpx: Comprehensive async testing framework
- Prereqs: Python 3.14+ with
uv, Docker + Docker Compose,psql. - Configure env:
cp .env.example .envand adjust DB credentials/ports as needed. - Start infra:
make db-upto boot PostgreSQL, thenmake db-migrateto apply migrations. - Install deps:
uv syncthenuv pip install -e .to install the app in editable mode. - Run API:
make api(serves on http://localhost:8000; OpenAPI at/docs). - Tests:
pytest(uses in-memory ASGI client; no external DB writes).
Authentication All endpoints except /auth/register require HTTP Basic Authentication.
- Register:
POST /auth/registerwith{"username":"u","password":"p"}. - Authenticate: pass HTTP Basic credentials (
u:p) on subsequent calls. - Accounts:
POST /accounts/creates an account for the authenticated user.GET /accounts/lists the user’s accounts;GET /accounts/{id}fetches one.
- Transactions:
POST /transactions/deposit{account_id, amount>0}adds funds to owned account.POST /transactions/withdrawal{account_id, amount>0}subtracts funds; 400 if insufficient balance.POST /transactions/transfer{from, to, amount>0}moves funds between distinct accounts; validates ownership offromand existence ofto.GET /transactions/account/{id}returns chronological transactions for an owned account.
- Error model: 401 for missing/invalid auth, 404 for missing resources, 400 for business rule violations (e.g., insufficient funds), 422 for schema/validation errors (e.g., non-positive amount).
- Pydantic models document request/response shapes (
CreateTransactionRequest,CreateTransferRequest,Transaction,CreateAccountResponse). - Inline docstrings describe each router’s responsibility and validation rules (auth, users, accounts, transactions).
- Validation enforced via Annotated/Pydantic constraints (e.g., positive
amount, distinct transfer accounts) keeps API contracts explicit and auto-documented in OpenAPI.
Critical Design Pattern: All financial operations use atomic SQL queries with Common Table Expressions (CTEs) to prevent race conditions:
WITH authorization_check AS (
-- Verify user owns the account
SELECT 1 FROM accounts a
JOIN users u ON a.user_id = u.id
WHERE a.id = $1 AND u.username = $2
),
account_update AS (
-- Update balance only if authorized
UPDATE accounts
SET balance = balance + $3
WHERE id = $1 AND EXISTS (SELECT 1 FROM authorization_check)
RETURNING id, balance
),
transaction_record AS (
-- Record transaction only if update succeeded
INSERT INTO transactions (from_account_id, to_account_id, amount)
SELECT $1, $2, $3
WHERE EXISTS (SELECT 1 FROM account_update)
RETURNING id
)
SELECT * FROM account_updateWhy This Matters:
Atomicity: All operations (check, update, record) happen in a single transaction No TOCTOU: Time-of-check to time-of-use vulnerabilities eliminated Consistency: Transaction records only created for successful operations Isolation: PostgreSQL's MVCC ensures concurrent transactions don't interfere
- FastAPI for async-first request handling and automatic OpenAPI generation; pairs well with Pydantic v2 for schema validation.
- asyncpg for low-latency PostgreSQL access; explicit SQL keeps control over locking and balance updates.
- HTTP Basic chosen for simplicity; credential verification occurs on every endpoint to scope queries per user.
- Transactional SQL blocks wrap balance mutations to ensure atomic deposits/withdrawals/transfers; business rules (ownership, sufficient funds) enforced in SQL plus Python guards.
- Tests use FastAPI’s ASGI transport + httpx AsyncClient to run quickly without network calls, mirroring real request flows.
- Trade-offs: Basic auth is not suitable for production (prefer OAuth/JWT); no rate limiting or multi-currency support; optimistic concurrency relies on SQL updates without explicit row locking beyond transaction scope.