Best Practices
This guide covers recommended practices for working with Forge-generated projects.
Project Structure
Keep the Generated Structure
The generated structure follows FastAPI conventions. Maintain this organization:
app/
├── core/ # Configuration, database, security
├── models/ # Database models
├── schemas/ # Pydantic schemas
├── crud/ # Database operations
├── services/ # Business logic
├── routers/v1/ # API endpoints
└── main.py # Application entry
Adding New Features
When adding new features, follow the existing patterns:
Model: Create in
app/models/Schema: Create in
app/schemas/CRUD: Create in
app/crud/Service (if needed): Create in
app/services/Router: Create in
app/routers/v1/Register router in
app/main.py
Example: Adding a Posts Feature
# 1. app/models/post.py
class Post(SQLModel, table=True):
id: Optional[int] = Field(primary_key=True)
title: str
content: str
author_id: int = Field(foreign_key="users.id")
# 2. app/schemas/post.py
class PostCreate(BaseModel):
title: str
content: str
class PostResponse(BaseModel):
id: int
title: str
content: str
author_id: int
# 3. app/crud/post.py
class PostCRUD:
@staticmethod
async def create(db: AsyncSession, post: PostCreate, author_id: int):
...
# 4. app/routers/v1/posts.py
router = APIRouter(prefix="/posts", tags=["Posts"])
@router.post("/", response_model=PostResponse)
async def create_post(...):
...
# 5. app/main.py - add import and include_router
Security
Environment Variables
Never commit secrets to version control:
# .gitignore should include:
secret/.env.development
secret/.env.production
Use .env.example as a template without real values.
JWT Configuration
Generate strong secrets: Use at least 32 random characters
python -c "import secrets; print(secrets.token_urlsafe(32))"
Keep access tokens short-lived: 15-30 minutes is recommended
Rotate secrets periodically: Update JWT_SECRET_KEY regularly
Password Security
The generated code uses Argon2 with secure defaults. Don’t weaken these settings.
CORS Configuration
In production, restrict allowed origins:
# Development
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080
# Production - be specific
CORS_ALLOWED_ORIGINS=https://myapp.com,https://www.myapp.com
Database
Connection Pool Tuning
Adjust pool settings based on your workload:
# For low-traffic applications
POOL_SIZE=3
POOL_MAX_OVERFLOW=2
# For high-traffic applications
POOL_SIZE=10
POOL_MAX_OVERFLOW=5
Migration Best Practices
Review auto-generated migrations: Always check before applying
Test migrations: Apply to a test database first
Backup before migrating: Especially in production
Use descriptive messages:
alembic revision -m "Add user email index"
Query Optimization
Use indexes for frequently queried fields:
class User(SQLModel, table=True):
email: str = Field(unique=True, index=True) # Indexed
username: str = Field(unique=True, index=True) # Indexed
API Design
Versioning
The generated structure uses /api/v1/ prefix. When making breaking changes:
Create new routers in
app/routers/v2/Keep v1 endpoints working
Deprecate v1 gradually
Error Handling
Use consistent error responses:
from fastapi import HTTPException
# Good - consistent format
raise HTTPException(
status_code=404,
detail="User not found"
)
# The global exception handler formats this as:
# {"status": 404, "error": "User not found"}
Pagination
For list endpoints, implement pagination:
@router.get("/users")
async def list_users(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
db: AsyncSession = Depends(get_db)
):
return await user_crud.get_all(db, skip=skip, limit=limit)
Performance
Async Operations
Use async throughout the request path:
# Good - async database operations
async def get_user(db: AsyncSession, user_id: int):
return await db.get(User, user_id)
# Avoid - blocking operations in async context
def get_user_sync(db: Session, user_id: int): # Don't do this
return db.get(User, user_id)
Caching with Redis
Cache expensive operations:
async def get_user_profile(user_id: int):
cache_key = f"user:profile:{user_id}"
# Try cache
cached = await redis_manager.get_async(cache_key)
if cached:
return json.loads(cached)
# Query and cache
profile = await fetch_profile(user_id)
await redis_manager.set_async(cache_key, json.dumps(profile), ex=300)
return profile
Background Tasks
Offload heavy operations to Celery:
@router.post("/reports")
async def generate_report(data: ReportRequest):
# Queue the task instead of processing inline
task = generate_report_task.delay(data.dict())
return {"task_id": task.id, "status": "processing"}
Testing
Test Coverage
Aim for high coverage on critical paths:
Authentication flows
Business logic in services
Data validation in schemas
Database operations in CRUD
Test Isolation
Each test should be independent:
@pytest.fixture
async def db_session():
# Fresh session for each test
# Rollback after test
# Clear data for isolation
Mock External Services
Don’t call real external services in tests:
from unittest.mock import patch
@pytest.mark.asyncio
async def test_send_email():
with patch('app.utils.email.email_service.send_email') as mock:
mock.return_value = None
# Test code that sends email
Logging
Use the Logger
The generated project includes Loguru configuration:
from app.core.logger import logger_manager
logger = logger_manager.get_logger(__name__)
logger.info("User registered", user_id=user.id)
logger.error("Database error", error=str(e))
Log Levels
DEBUG: Detailed information for debuggingINFO: General operational eventsWARNING: Something unexpected but not criticalERROR: Something failed
Code Quality
Type Hints
Use type hints throughout:
async def get_user(db: AsyncSession, user_id: int) -> Optional[User]:
return await db.get(User, user_id)
Development Tools
If dev_tools is enabled, use them:
# Format code
black .
# Lint code
ruff check .
# Type check
mypy app/