A RESTful financial ledger API built with Django REST Framework and PostgreSQL for managing user accounts, financial transactions, and double-entry ledger records.
The project focuses on secure authentication, reliable money transfers, transaction integrity, idempotency, concurrency handling, and production deployment.
- User registration and authentication
- JWT-based authentication
- Access and refresh tokens
- Refresh token rotation and blacklisting
- User account management
- Financial account balances
- Money transfers between accounts
- Double-entry ledger records
- Transaction status tracking
- Idempotent transfers using idempotency keys
- Database transaction atomicity
- Row-level locking for concurrent transfers
- Currency validation
- Account status validation
- Transaction history
- Transaction filtering
- Pagination
- API throttling
- OpenAPI schema and Swagger documentation
- Automated tests
- Docker containerization
- PostgreSQL database
- Production deployment with Render and Neon
| Technology | Purpose |
|---|---|
| Python | Backend programming language |
| Django | Web framework |
| Django REST Framework | REST API development |
| PostgreSQL | Relational database |
| SimpleJWT | JWT authentication |
| drf-spectacular | OpenAPI/Swagger documentation |
| Docker | Containerization |
| Gunicorn | Production WSGI server |
| Nginx | Reverse proxy for local container setup |
| Render | Production application hosting |
| Neon | Production PostgreSQL database |
| uv | Python dependency and environment management |
| Git/GitHub | Version control |
Client
│
▼
REST API
│
▼
Django REST Framework
│
├── Authentication / Permissions
│
├── Serializers
│
├── Views / ViewSets
│
▼
Service Layer
│
├── Transfer validation
├── Idempotency
├── Atomic transactions
└── Concurrency control
│
▼
PostgreSQL
│
├── Accounts
├── Transactions
└── Ledger Entries
Internet
│
▼
Render
│
▼
Django + Gunicorn
│
▼
Neon PostgreSQL
The API uses JWT authentication.
Users obtain access and refresh tokens through the authentication endpoints.
Access tokens are short-lived, while refresh tokens can be used to obtain new access tokens.
Refresh token rotation and blacklisting are enabled to prevent reuse of rotated refresh tokens.
Protected endpoints require:
Authorization: Bearer <access_token>POST /api/auth/register/
POST /api/auth/login/
POST /api/auth/token/refresh/
GET /api/accounts/
GET /api/accounts/{id}/
GET /api/transactions/
GET /api/transactions/{id}/
POST /api/transactions/transfer/
GET /api/schema/
GET /api/docs/
Transfers are handled inside a database transaction.
The transfer process validates:
- The sender and receiver are different accounts.
- Both accounts exist.
- Both accounts are active.
- Both accounts use the same currency.
- The idempotency key has not been used for a different transaction.
- The sender has sufficient funds.
The accounts are locked using database row-level locking before the balance is checked.
This prevents concurrent transfers from spending the same balance.
Conceptually:
Request
│
▼
Validate transfer
│
▼
Lock accounts
│
▼
Check idempotency
│
▼
Check balance
│
▼
Create transaction
│
▼
Create debit ledger entry
│
▼
Create credit ledger entry
│
▼
Mark transaction COMPLETED
If any operation fails, the database transaction is rolled back.
Transfers require an idempotency_key.
This prevents accidental duplicate processing when the same request is submitted more than once.
For example:
{
"from_account_id": 1,
"to_account_id": 2,
"amount": "100.00",
"idempotency_key": "transfer-001"
}If the same request is submitted again with the same key, the existing transaction can be returned instead of creating another transfer.
If the same key is reused with different transaction details, the request is rejected.
The system records transfers using ledger entries.
A successful transfer creates:
Sender Account
│
└── DEBIT 100.00
Receiver Account
│
└── CREDIT 100.00
The ledger provides a transaction history from which account balances can be calculated.
The transfer service uses Django's database transaction management:
with transaction.atomic():
...This ensures that related database operations succeed or fail together.
For example, if the debit entry succeeds but the credit entry fails, the entire transfer is rolled back.
The transfer service uses:
select_for_update()to lock the accounts involved in a transfer.
Accounts are locked in a consistent order to reduce the risk of deadlocks.
This is important when multiple requests attempt to transfer money from the same account at the same time.
The API validates:
- Positive transaction amounts
- Sender and receiver accounts
- Account status
- Currency compatibility
- Sufficient balance
- Idempotency keys
- User authorization
Example insufficient-balance response:
{
"detail": "Insufficient balance. Current balance is 0.00. Requested amount is 100.00."
}Transaction history supports pagination and filtering.
Example:
GET /api/transactions/
Response:
{
"count": 10,
"next": "...",
"previous": null,
"results": []
}The API uses DRF throttling to limit request rates.
Configured limits include:
Anonymous users: 20 requests/minute
Authenticated users: 60 requests/minute
This provides basic protection against excessive API requests.
The project includes automated tests covering areas such as:
- Successful transfers
- Insufficient funds
- Same-account transfers
- Invalid accounts
- Inactive accounts
- Currency mismatch
- Transaction rollback
- Idempotency
- Concurrent transfers
- Authorization
- Transaction filtering
- Transaction status filtering
- Pagination
The test suite has previously passed with:
Ran 33 tests
OK
Sensitive configuration is stored in environment variables rather than committed to Git.
Example:
SECRET_KEY=your-secret-key
DEBUG=False
DATABASE_URL=your-postgresql-connection-string
SECURE_SSL_REDIRECT=TrueThe .env file is excluded from version control.
Clone the repository:
git clone https://github.com/mucheru-delvan/FinancialLedgerAPI.git
cd FinancialLedgerAPICreate and activate the virtual environment:
uv sync
source .venv/bin/activateRun migrations:
uv run python manage.py migrateStart the development server:
uv run python manage.py runserverThe API will be available at:
http://127.0.0.1:8000/
Swagger documentation:
http://127.0.0.1:8000/api/docs/
The project includes Docker support for running the application in containers.
Build the image:
docker build -t financial-ledger-api .Run the container:
docker run --rm \
-p 8000:8000 \
-e PORT=8000 \
--env-file .env \
financial-ledger-apiThe project also includes Docker Compose for running Django, PostgreSQL, and Nginx together.
docker compose up --buildThe API is deployed using:
- Render for the Django application
- Neon for PostgreSQL
Production architecture:
Client
│
▼
Render
│
▼
Docker
│
▼
Gunicorn
│
▼
Django REST Framework
│
▼
Neon PostgreSQL
Swagger:
https://financialledgerapi.onrender.com/api/docs/
Security measures implemented include:
- JWT authentication
- Short-lived access tokens
- Refresh token rotation
- Refresh token blacklisting
- Password hashing through Django
- Authenticated API permissions
- Environment-based secrets
- HTTPS in production
- API throttling
- Database transactions
- Row-level locking
- Idempotent transaction processing
- Input validation
This project was built to demonstrate practical backend engineering concepts relevant to financial systems, including:
- REST API design
- Authentication and authorization
- Relational database design
- Transaction processing
- ACID transactions
- Concurrency control
- Idempotency
- Testing
- Containerization
- Production deployment