Project Structure
FastAPI AI Kit follows a clean, domain-driven layout. Every module is decoupled — delete what you don't need without breaking the rest.
app/
├── api/ # FastAPI routers
│ ├── auth.py # Register, login, token refresh
│ ├── api_keys.py # Issue, list, revoke API keys
│ ├── chat.py # /v1/chat — LLM endpoint (streaming + buffered)
│ ├── rag.py # /v1/rag — ingest + query endpoints
│ └── health.py # /healthz
│
├── core/ # Cross-cutting concerns
│ ├── config.py # Settings via Pydantic BaseSettings
│ ├── database.py # Async SQLAlchemy engine + session factory
│ ├── redis.py # Redis connection pool
│ ├── security.py # JWT encode/decode, password hashing
│ └── middleware.py # Rate limiting, request logging
│
├── models/ # SQLAlchemy ORM models
│ ├── user.py
│ ├── api_key.py
│ └── usage.py # Token usage records per key
│
├── schemas/ # Pydantic request/response models
│ ├── auth.py
│ ├── chat.py
│ └── rag.py
│
├── services/ # Business logic layer
│ ├── auth_service.py # User registration, token issuance
│ ├── llm_service.py # Unified LLM abstraction
│ ├── rag_service.py # Document ingestion + retrieval
│ ├── billing_service.py # Usage metering, Stripe hooks
│ └── key_service.py # API key lifecycle
│
├── repositories/ # Data access layer (DB queries)
│ ├── user_repo.py
│ ├── key_repo.py
│ └── usage_repo.py
│
├── workers/ # Background tasks
│ ├── celery_app.py
│ ├── tasks.py # Async jobs (embedding, email, etc.)
│ └── arq_app.py # arq alternative (lighter weight)
│
├── migrations/ # Alembic migration scripts
└── tests/ # pytest test suite
├── conftest.py
├── test_auth.py
├── test_chat.py
└── test_rag.py
Key design decisions
Services over fat routes
Business logic lives in services/, not in route handlers. Routes validate input, call a service, and return the result. This keeps routes thin and testable.
Repositories for DB access
Database queries are isolated in repositories/. Services call repositories, never raw SQLAlchemy sessions directly. This makes testing straightforward — swap the real repo for a mock.
Config via Pydantic BaseSettings
All configuration flows through app/core/config.py. Environment variables are validated on startup. Accessing settings.OPENAI_API_KEY is always type-safe and never None if the app booted.
Async everywhere
The codebase is async from top to bottom — FastAPI, SQLAlchemy async, aioredis. No blocking calls on the event loop. This matters at scale.
