initial commit

This commit is contained in:
2025-10-19 22:09:35 +03:00
commit 6d593b4554
114 changed files with 23622 additions and 0 deletions

View File

@ -0,0 +1,651 @@
# Configuration Guide
This guide covers all configuration options available in the FastAPI Boilerplate, including environment variables, settings classes, and advanced deployment configurations.
## Configuration Overview
The boilerplate uses a layered configuration approach:
- **Environment Variables** (`.env` file) - Primary configuration method
- **Settings Classes** (`src/app/core/config.py`) - Python-based configuration
- **Docker Configuration** (`docker-compose.yml`) - Container orchestration
- **Database Configuration** (`alembic.ini`) - Database migrations
## Environment Variables Reference
All configuration is managed through environment variables defined in the `.env` file located in the `src/` directory.
### Application Settings
Basic application metadata displayed in API documentation:
```env
# ------------- app settings -------------
APP_NAME="Your App Name"
APP_DESCRIPTION="Your app description here"
APP_VERSION="0.1.0"
CONTACT_NAME="Your Name"
CONTACT_EMAIL="your.email@example.com"
LICENSE_NAME="MIT"
```
**Variables Explained:**
- `APP_NAME`: Displayed in API documentation and responses
- `APP_DESCRIPTION`: Shown in OpenAPI documentation
- `APP_VERSION`: API version for documentation and headers
- `CONTACT_NAME`: Contact information for API documentation
- `CONTACT_EMAIL`: Support email for API users
- `LICENSE_NAME`: License type for the API
### Database Configuration
PostgreSQL database connection settings:
```env
# ------------- database -------------
POSTGRES_USER="your_postgres_user"
POSTGRES_PASSWORD="your_secure_password"
POSTGRES_SERVER="localhost"
POSTGRES_PORT=5432
POSTGRES_DB="your_database_name"
```
**Variables Explained:**
- `POSTGRES_USER`: Database user with appropriate permissions
- `POSTGRES_PASSWORD`: Strong password for database access
- `POSTGRES_SERVER`: Hostname or IP of PostgreSQL server
- `POSTGRES_PORT`: PostgreSQL port (default: 5432)
- `POSTGRES_DB`: Name of the database to connect to
**Environment-Specific Values:**
```env
# Local development
POSTGRES_SERVER="localhost"
# Docker Compose
POSTGRES_SERVER="db"
# Production
POSTGRES_SERVER="your-prod-db-host.com"
```
### Security & Authentication
JWT and password security configuration:
```env
# ------------- crypt -------------
SECRET_KEY="your-super-secret-key-here"
ALGORITHM="HS256"
ACCESS_TOKEN_EXPIRE_MINUTES=30
REFRESH_TOKEN_EXPIRE_DAYS=7
```
**Variables Explained:**
- `SECRET_KEY`: Used for JWT token signing (generate with `openssl rand -hex 32`)
- `ALGORITHM`: JWT signing algorithm (HS256 recommended)
- `ACCESS_TOKEN_EXPIRE_MINUTES`: How long access tokens remain valid
- `REFRESH_TOKEN_EXPIRE_DAYS`: How long refresh tokens remain valid
!!! danger "Security Warning"
Never use default values in production. Generate a strong secret key:
```bash
openssl rand -hex 32
```
### Redis Configuration
Redis is used for caching, job queues, and rate limiting:
```env
# ------------- redis cache -------------
REDIS_CACHE_HOST="localhost" # Use "redis" for Docker Compose
REDIS_CACHE_PORT=6379
# ------------- redis queue -------------
REDIS_QUEUE_HOST="localhost" # Use "redis" for Docker Compose
REDIS_QUEUE_PORT=6379
# ------------- redis rate limit -------------
REDIS_RATE_LIMIT_HOST="localhost" # Use "redis" for Docker Compose
REDIS_RATE_LIMIT_PORT=6379
```
**Best Practices:**
- **Development**: Use the same Redis instance for all services
- **Production**: Use separate Redis instances for better isolation
```env
# Production example with separate instances
REDIS_CACHE_HOST="cache.redis.example.com"
REDIS_QUEUE_HOST="queue.redis.example.com"
REDIS_RATE_LIMIT_HOST="ratelimit.redis.example.com"
```
### Caching Settings
Client-side and server-side caching configuration:
```env
# ------------- redis client-side cache -------------
CLIENT_CACHE_MAX_AGE=30 # seconds
```
**Variables Explained:**
- `CLIENT_CACHE_MAX_AGE`: How long browsers should cache responses
### Rate Limiting
Default rate limiting configuration:
```env
# ------------- default rate limit settings -------------
DEFAULT_RATE_LIMIT_LIMIT=10 # requests per period
DEFAULT_RATE_LIMIT_PERIOD=3600 # period in seconds (1 hour)
```
**Variables Explained:**
- `DEFAULT_RATE_LIMIT_LIMIT`: Number of requests allowed per period
- `DEFAULT_RATE_LIMIT_PERIOD`: Time window in seconds
### Admin User
First superuser account configuration:
```env
# ------------- admin -------------
ADMIN_NAME="Admin User"
ADMIN_EMAIL="admin@example.com"
ADMIN_USERNAME="admin"
ADMIN_PASSWORD="secure_admin_password"
```
**Variables Explained:**
- `ADMIN_NAME`: Display name for the admin user
- `ADMIN_EMAIL`: Email address for the admin account
- `ADMIN_USERNAME`: Username for admin login
- `ADMIN_PASSWORD`: Initial password (change after first login)
### User Tiers
Initial tier configuration:
```env
# ------------- first tier -------------
TIER_NAME="free"
```
**Variables Explained:**
- `TIER_NAME`: Name of the default user tier
### Environment Type
Controls API documentation visibility and behavior:
```env
# ------------- environment -------------
ENVIRONMENT="local" # local, staging, or production
```
**Environment Types:**
- **local**: Full API docs available publicly at `/docs`
- **staging**: API docs available to superusers only
- **production**: API docs completely disabled
## Docker Compose Configuration
### Basic Setup
Docker Compose automatically loads the `.env` file:
```yaml
# In docker-compose.yml
services:
web:
env_file:
- ./src/.env
```
### Development Overrides
Create `docker-compose.override.yml` for local customizations:
```yaml
version: '3.8'
services:
web:
ports:
- "8001:8000" # Use different port
environment:
- DEBUG=true
volumes:
- ./custom-logs:/code/logs
```
### Service Configuration
Understanding each Docker service:
```yaml
services:
web: # FastAPI application
db: # PostgreSQL database
redis: # Redis for caching/queues
worker: # ARQ background task worker
nginx: # Reverse proxy (optional)
```
## Python Settings Classes
Advanced configuration is handled in `src/app/core/config.py`:
### Settings Composition
The main `Settings` class inherits from multiple setting groups:
```python
class Settings(
AppSettings,
PostgresSettings,
CryptSettings,
FirstUserSettings,
RedisCacheSettings,
ClientSideCacheSettings,
RedisQueueSettings,
RedisRateLimiterSettings,
DefaultRateLimitSettings,
EnvironmentSettings,
):
pass
```
### Adding Custom Settings
Create your own settings group:
```python
class CustomSettings(BaseSettings):
CUSTOM_API_KEY: str = ""
CUSTOM_TIMEOUT: int = 30
ENABLE_FEATURE_X: bool = False
# Add to main Settings class
class Settings(
AppSettings,
# ... other settings ...
CustomSettings,
):
pass
```
### Opting Out of Services
Remove unused services by excluding their settings:
```python
# Minimal setup without Redis services
class Settings(
AppSettings,
PostgresSettings,
CryptSettings,
FirstUserSettings,
# Removed: RedisCacheSettings
# Removed: RedisQueueSettings
# Removed: RedisRateLimiterSettings
EnvironmentSettings,
):
pass
```
## Database Configuration
### Alembic Configuration
Database migrations are configured in `src/alembic.ini`:
```ini
[alembic]
script_location = migrations
sqlalchemy.url = postgresql://%(POSTGRES_USER)s:%(POSTGRES_PASSWORD)s@%(POSTGRES_SERVER)s:%(POSTGRES_PORT)s/%(POSTGRES_DB)s
```
### Connection Pooling
SQLAlchemy connection pool settings in `src/app/core/db/database.py`:
```python
engine = create_async_engine(
DATABASE_URL,
pool_size=20, # Number of connections to maintain
max_overflow=30, # Additional connections allowed
pool_timeout=30, # Seconds to wait for connection
pool_recycle=1800, # Seconds before connection refresh
)
```
### Database Best Practices
**Connection Pool Sizing:**
- Start with `pool_size=20`, `max_overflow=30`
- Monitor connection usage and adjust based on load
- Use connection pooling monitoring tools
**Migration Strategy:**
- Always backup database before running migrations
- Test migrations on staging environment first
- Use `alembic revision --autogenerate` for model changes
## Security Configuration
### JWT Token Configuration
Customize JWT behavior in `src/app/core/security.py`:
```python
def create_access_token(data: dict, expires_delta: timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
)
```
### CORS Configuration
Configure Cross-Origin Resource Sharing in `src/app/main.py`:
```python
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"], # Specify allowed origins
allow_credentials=True,
allow_methods=["GET", "POST"], # Specify allowed methods
allow_headers=["*"],
)
```
**Production CORS Settings:**
```python
# Never use wildcard (*) in production
allow_origins=[
"https://yourapp.com",
"https://www.yourapp.com"
],
```
### Security Headers
Add security headers middleware:
```python
from starlette.middleware.base import BaseHTTPMiddleware
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
response = await call_next(request)
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-XSS-Protection"] = "1; mode=block"
return response
```
## Logging Configuration
### Basic Logging Setup
Configure logging in `src/app/core/logger.py`:
```python
import logging
from logging.handlers import RotatingFileHandler
# Set log level
LOGGING_LEVEL = logging.INFO
# Configure file rotation
file_handler = RotatingFileHandler(
'logs/app.log',
maxBytes=10485760, # 10MB
backupCount=5 # Keep 5 backup files
)
```
### Structured Logging
Use structured logging for better observability:
```python
import structlog
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.processors.JSONRenderer()
],
logger_factory=structlog.stdlib.LoggerFactory(),
)
```
### Log Levels by Environment
```python
# Environment-specific log levels
LOG_LEVELS = {
"local": logging.DEBUG,
"staging": logging.INFO,
"production": logging.WARNING
}
LOGGING_LEVEL = LOG_LEVELS.get(settings.ENVIRONMENT, logging.INFO)
```
## Environment-Specific Configurations
### Development (.env.development)
```env
ENVIRONMENT="local"
POSTGRES_SERVER="localhost"
REDIS_CACHE_HOST="localhost"
SECRET_KEY="dev-secret-key-not-for-production"
ACCESS_TOKEN_EXPIRE_MINUTES=60 # Longer for development
DEBUG=true
```
### Staging (.env.staging)
```env
ENVIRONMENT="staging"
POSTGRES_SERVER="staging-db.example.com"
REDIS_CACHE_HOST="staging-redis.example.com"
SECRET_KEY="staging-secret-key-different-from-prod"
ACCESS_TOKEN_EXPIRE_MINUTES=30
DEBUG=false
```
### Production (.env.production)
```env
ENVIRONMENT="production"
POSTGRES_SERVER="prod-db.example.com"
REDIS_CACHE_HOST="prod-redis.example.com"
SECRET_KEY="ultra-secure-production-key-generated-with-openssl"
ACCESS_TOKEN_EXPIRE_MINUTES=15
DEBUG=false
REDIS_CACHE_PORT=6380 # Custom port for security
POSTGRES_PORT=5433 # Custom port for security
```
## Advanced Configuration
### Custom Middleware
Add custom middleware in `src/app/core/setup.py`:
```python
def create_application(router, settings, **kwargs):
app = FastAPI(...)
# Add custom middleware
app.add_middleware(CustomMiddleware, setting=value)
app.add_middleware(TimingMiddleware)
app.add_middleware(RequestIDMiddleware)
return app
```
### Feature Toggles
Implement feature flags:
```python
class FeatureSettings(BaseSettings):
ENABLE_ADVANCED_CACHING: bool = False
ENABLE_ANALYTICS: bool = True
ENABLE_EXPERIMENTAL_FEATURES: bool = False
ENABLE_API_VERSIONING: bool = True
# Use in endpoints
if settings.ENABLE_ADVANCED_CACHING:
# Advanced caching logic
pass
```
### Health Checks
Configure health check endpoints:
```python
@app.get("/health")
async def health_check():
return {
"status": "healthy",
"database": await check_database_health(),
"redis": await check_redis_health(),
"version": settings.APP_VERSION
}
```
## Configuration Validation
### Environment Validation
Add validation to prevent misconfiguration:
```python
def validate_settings():
if not settings.SECRET_KEY:
raise ValueError("SECRET_KEY must be set")
if settings.ENVIRONMENT == "production":
if settings.SECRET_KEY == "dev-secret-key":
raise ValueError("Production must use secure SECRET_KEY")
if settings.DEBUG:
raise ValueError("DEBUG must be False in production")
```
### Runtime Checks
Add validation to application startup:
```python
@app.on_event("startup")
async def startup_event():
validate_settings()
await check_database_connection()
await check_redis_connection()
logger.info(f"Application started in {settings.ENVIRONMENT} mode")
```
## Configuration Troubleshooting
### Common Issues
**Environment Variables Not Loading:**
```bash
# Check file location and permissions
ls -la src/.env
# Check file format (no spaces around =)
cat src/.env | grep "=" | head -5
# Verify environment loading in Python
python -c "from src.app.core.config import settings; print(settings.APP_NAME)"
```
**Database Connection Failed:**
```bash
# Test connection manually
psql -h localhost -U postgres -d myapp
# Check if PostgreSQL is running
systemctl status postgresql
# or on macOS
brew services list | grep postgresql
```
**Redis Connection Failed:**
```bash
# Test Redis connection
redis-cli -h localhost -p 6379 ping
# Check Redis status
systemctl status redis
# or on macOS
brew services list | grep redis
```
### Configuration Testing
Test your configuration with a simple script:
```python
# test_config.py
import asyncio
from src.app.core.config import settings
from src.app.core.db.database import async_get_db
async def test_config():
print(f"App: {settings.APP_NAME}")
print(f"Environment: {settings.ENVIRONMENT}")
# Test database
try:
db = await anext(async_get_db())
print("✓ Database connection successful")
await db.close()
except Exception as e:
print(f"✗ Database connection failed: {e}")
# Test Redis (if enabled)
try:
from src.app.core.utils.cache import redis_client
await redis_client.ping()
print("✓ Redis connection successful")
except Exception as e:
print(f"✗ Redis connection failed: {e}")
if __name__ == "__main__":
asyncio.run(test_config())
```
Run with:
```bash
uv run python test_config.py
```