Skip to content

Repository files navigation

fastapi-finance-api

CI Python 3.11+ License: MIT

A production-grade Personal Finance Manager REST API built with FastAPI, SQLAlchemy 2.0 async, and PostgreSQL. Demonstrates modern Python backend engineering: Clean Architecture, async/await, JWT authentication, and comprehensive testing.

Built as a portfolio project applicable to fintech, SaaS, and any data-driven Python backend.


Architecture

Follows Clean Architecture (Hexagonal) with 4 layers:

┌──────────────────────────────────────────────────────────────────┐
│  presentation/   FastAPI routers, request/response schemas        │
├──────────────────────────────────────────────────────────────────┤
│  application/    Services (use cases), Pydantic schemas           │
├──────────────────────────────────────────────────────────────────┤
│  infrastructure/ SQLAlchemy ORM models, async repositories        │
├──────────────────────────────────────────────────────────────────┤
│  domain/         Pure Python dataclasses, domain exceptions       │
│                  (no framework dependencies)                      │
└──────────────────────────────────────────────────────────────────┘

Features

Feature Details
JWT Auth Access token (30 min) + Refresh token (7 days) — python-jose
Password security bcrypt hashing — passlib
Async everything SQLAlchemy 2.0 async + asyncpg — no blocking I/O
Accounts CRUD — checking, savings, investment, credit with balance tracking
Transactions CRUD — income/expense/transfer with category, tags, pagination
Analytics Spending by category + monthly income vs expenses
Validation Pydantic v2 — password strength, email, decimal precision
Rate limiting 60 req/min per IP via slowapi
CORS Configurable allowed origins
Background tasks Welcome email via FastAPI BackgroundTasks
DB migrations Alembic async migrations
Structured logging structlog with request logging middleware
OpenAPI docs Auto-generated Swagger UI + ReDoc
Tests Unit (Mockito-style mocks) + integration (httpx AsyncClient + SQLite)

Tech Stack

  • Python 3.11 — dataclasses, match statements, X | Y type hints
  • FastAPI 0.111 — async, dependency injection, OpenAPI
  • SQLAlchemy 2.0 — async ORM (mapped_column, Mapped types)
  • asyncpg — high-performance async PostgreSQL driver
  • Alembic — database migrations (async-compatible)
  • Pydantic v2 — request/response validation, settings
  • python-jose — JWT encoding/decoding
  • passlib + bcrypt — password hashing
  • slowapi — FastAPI rate limiting
  • structlog — structured JSON logging
  • pytest + httpx — async test client
  • Docker / Docker Compose — one-command local setup

Getting Started

Prerequisites

  • Python 3.11+
  • Docker & Docker Compose

Quick start with Docker

git clone https://github.com/M-TOUITI/fastapi-finance-api.git
cd fastapi-finance-api

docker-compose up -d

# API: http://localhost:8000/api/v1/docs
# pgAdmin: http://localhost:5050  (admin@demo.com / admin)

Local development

# 1. Install dependencies
pip install -r requirements.txt

# 2. Copy and configure environment
cp .env.example .env

# 3. Start PostgreSQL
docker-compose up -d postgres

# 4. Run migrations
make migrate

# 5. Start dev server (with auto-reload)
make dev
# → http://localhost:8000/api/v1/docs

Run tests

# All tests with coverage
make test

# Unit tests only (no DB needed)
make test-unit

# Integration tests
make test-integration

API Reference

All endpoints are documented at /api/v1/docs (Swagger UI).

Authentication

# Register
POST /api/v1/auth/register
{
  "email": "user@example.com",
  "password": "Password123",
  "full_name": "John Doe"
}

# Login → get tokens
POST /api/v1/auth/login
{
  "email": "user@example.com",
  "password": "Password123"
}
# Response: { "access_token": "...", "refresh_token": "...", "token_type": "bearer" }

# Refresh access token
POST /api/v1/auth/refresh
{ "refresh_token": "..." }

Accounts

# Create account
POST /api/v1/accounts
Authorization: Bearer <token>
{
  "name": "Main Checking",
  "account_type": "checking",   # checking | savings | investment | credit
  "currency": "EUR",
  "initial_balance": "1000.00"
}

# List accounts
GET /api/v1/accounts

# Get / Update / Delete
GET    /api/v1/accounts/{id}
PATCH  /api/v1/accounts/{id}
DELETE /api/v1/accounts/{id}

Transactions

# Create transaction
POST /api/v1/transactions
{
  "account_id": "uuid",
  "transaction_type": "expense",   # income | expense | transfer
  "amount": "85.50",
  "description": "Grocery shopping",
  "category": "food",
  "transaction_date": "2025-06-15T14:00:00",
  "tags": ["weekly", "organic"]
}

# List with filters + pagination
GET /api/v1/transactions
    ?account_id=uuid
    &category=food
    &transaction_type=expense
    &date_from=2025-06-01
    &date_to=2025-06-30
    &page=1
    &page_size=20

Analytics

# Spending by category (pie chart data)
GET /api/v1/analytics/spending-by-category
    ?date_from=2025-06-01&date_to=2025-06-30

# Response:
{
  "spending_by_category": [
    {"category": "food",    "total": 350.0, "count": 8, "percentage": 35.0},
    {"category": "housing", "total": 1200.0, "count": 1, "percentage": 65.0}
  ],
  "total_spending": 1550.0
}

# Monthly summary (bar chart data)
GET /api/v1/analytics/monthly-summary?year=2025

# Response:
{
  "year": 2025,
  "total_income": 36000.0,
  "total_expenses": 28000.0,
  "net_savings": 8000.0,
  "savings_rate": 22.2,
  "monthly_totals": [
    {"month": 1, "month_name": "January", "income": 3000.0, "expenses": 2300.0, "net": 700.0},
    ...
  ]
}

Project Structure

fastapi-finance-api/
├── app/
│   ├── main.py                   # FastAPI app factory, middleware, routers
│   ├── core/
│   │   ├── config.py             # pydantic-settings — env vars
│   │   ├── security.py           # JWT + bcrypt
│   │   └── exceptions.py         # exception handlers
│   ├── domain/
│   │   ├── models/               # Pure Python dataclasses (no ORM)
│   │   └── exceptions.py         # Domain exceptions
│   ├── infrastructure/
│   │   ├── database/
│   │   │   ├── models.py         # SQLAlchemy 2.0 ORM models
│   │   │   └── session.py        # Async session + get_db dependency
│   │   └── repositories/         # Async repository classes
│   ├── application/
│   │   ├── services/             # Business logic / use cases
│   │   └── schemas/              # Pydantic v2 request/response schemas
│   └── presentation/
│       └── api/v1/
│           ├── dependencies.py   # FastAPI DI (repos, services, current_user)
│           └── routers/          # auth, accounts, transactions, analytics
├── tests/
│   ├── conftest.py               # Fixtures (client, db_session, auth_headers)
│   ├── unit/services/            # Service tests with mocks
│   └── integration/              # Full API tests with httpx AsyncClient
├── alembic/                      # DB migrations
├── docker-compose.yml
├── Dockerfile
├── Makefile
└── requirements.txt

Design Decisions

Why async SQLAlchemy 2.0? Async I/O allows the API to handle many concurrent requests without threads. With asyncpg, PostgreSQL queries are truly non-blocking — critical for high-throughput finance APIs where many users query dashboards simultaneously.

Why Clean Architecture? Each layer depends only inward. The domain and application layers have zero framework dependencies — services can be unit-tested without spinning up FastAPI or a database, using simple Python mocks.

Why Annotated for dependency injection? Annotated[SomeType, Depends(get_something)] keeps router signatures clean and type-safe. It also makes the dependencies explicit without cluttering function bodies.

Why soft-delete for accounts? Setting is_active=False preserves referential integrity — transactions linked to the account remain intact for audit/reporting. Hard-deleting an account would cascade-delete its transactions, losing financial history.


License

MIT

About

Production-grade Personal Finance Manager REST API built with FastAPI, async SQLAlchemy 2.0, and PostgreSQL. Clean Architecture, JWT auth, accounts & transactions, spending analytics, rate limiting, Alembic migrations, and full async test coverage

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages