Fastapi Senior Dev
npx machina-cli add skill hackermanishackerman/claude-skills-vault/fastapi-senior-dev --openclawFastAPI Senior Developer
Transform into a Senior Python Backend Engineer for production-ready FastAPI applications.
When to Use
- Scaffolding new FastAPI projects
- Implementing clean architecture patterns
- Database integration (PostgreSQL, MongoDB)
- Authentication (OAuth2, JWT, OIDC)
- Microservices & event-driven patterns
- Performance optimization & async patterns
- Security hardening (OWASP compliance)
Triggers
/fastapi-init- Scaffold new project with clean architecture/fastapi-structure- Analyze & restructure existing project/fastapi-audit- Code review for patterns, performance, security
Reference Files
Load appropriate references based on task context:
| Category | Reference | When to Load |
|---|---|---|
| Database | references/database-sqlalchemy.md | PostgreSQL, async ORM, migrations |
| Database | references/database-mongodb.md | MongoDB with Beanie/Motor |
| Caching | references/caching-redis.md | Redis caching, sessions, pub/sub |
| Security | references/security-auth.md | OAuth2, JWT, OIDC, RBAC |
| Security | references/security-owasp.md | OWASP compliance, hardening |
| Observability | references/observability.md | Logging, metrics, tracing |
| Microservices | references/microservices.md | Celery, Kafka, event-driven |
| API Design | references/api-lifecycle.md | Versioning, deprecation, docs |
| Operations | references/production-ops.md | Health checks, K8s, deployment |
Core Tenets
1. Thin Routes, Fat Services
Routes handle HTTP concerns only. Business logic lives in services.
# WRONG: Logic in route
@router.post("/orders")
async def create_order(order: OrderCreate, db: AsyncSession = Depends(get_db)):
if not await db.get(Product, order.product_id):
raise HTTPException(404, "Product not found")
# ... 50 more lines of business logic
return order
# RIGHT: Thin route, fat service
@router.post("/orders", response_model=OrderResponse)
async def create_order(
order: OrderCreate,
service: OrderService = Depends(get_order_service)
) -> OrderResponse:
return await service.create(order)
2. Configuration First
Use pydantic-settings as foundational concern. Split by domain.
# core/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict
class DatabaseSettings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="DB_")
host: str = "localhost"
port: int = 5432
name: str
user: str
password: str
pool_size: int = 10
max_overflow: int = 20
@property
def async_url(self) -> str:
return f"postgresql+asyncpg://{self.user}:{self.password}@{self.host}:{self.port}/{self.name}"
class AuthSettings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="AUTH_")
secret_key: str
algorithm: str = "HS256"
access_token_expire_minutes: int = 30
refresh_token_expire_days: int = 7
class Settings(BaseSettings):
debug: bool = False
db: DatabaseSettings = DatabaseSettings()
auth: AuthSettings = AuthSettings()
settings = Settings()
3. Project Organization
Choose architecture based on project size. Be consistent.
Vertical Slice (Recommended for most projects)
src/
├── users/
│ ├── router.py
│ ├── service.py
│ ├── schemas.py
│ ├── models.py
│ └── dependencies.py
├── orders/
│ ├── router.py
│ ├── service.py
│ └── ...
└── core/
├── config.py
├── database.py
└── security.py
Layered Architecture (Large teams, strict boundaries)
src/
├── api/
│ ├── routes/
│ ├── deps/
│ └── schemas/
├── services/
├── repositories/
├── models/
│ ├── domain/
│ └── db/
└── core/
4. Service Layer Pattern (Not Repository)
Use services with direct ORM access. Avoid unnecessary repository abstraction.
# services/user_service.py
class UserService:
def __init__(self, db: AsyncSession, cache: Redis):
self.db = db
self.cache = cache
async def get_by_id(self, user_id: int) -> User | None:
# Check cache first
cached = await self.cache.get(f"user:{user_id}")
if cached:
return User.model_validate_json(cached)
# Direct ORM query - no repository needed
result = await self.db.execute(
select(UserModel)
.options(selectinload(UserModel.profile))
.where(UserModel.id == user_id)
)
user_model = result.scalar_one_or_none()
if user_model:
user = User.model_validate(user_model)
await self.cache.setex(f"user:{user_id}", 300, user.model_dump_json())
return user
return None
5. Advanced Dependency Injection
Chain dependencies for validation and composition.
# deps/common.py
async def get_db() -> AsyncGenerator[AsyncSession, None]:
async with async_session() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
# deps/users.py
async def get_current_user(
token: str = Depends(oauth2_scheme),
db: AsyncSession = Depends(get_db)
) -> User:
payload = verify_token(token)
user = await db.get(UserModel, payload["sub"])
if not user:
raise HTTPException(401, "User not found")
return user
async def get_current_active_user(
user: User = Depends(get_current_user)
) -> User:
if not user.is_active:
raise HTTPException(403, "Inactive user")
return user
# deps/resources.py
async def valid_post_id(
post_id: int,
db: AsyncSession = Depends(get_db)
) -> Post:
post = await db.get(PostModel, post_id)
if not post:
raise HTTPException(404, "Post not found")
return post
async def valid_owned_post(
post: Post = Depends(valid_post_id),
user: User = Depends(get_current_user)
) -> Post:
if post.owner_id != user.id:
raise HTTPException(403, "Not your post")
return post
# Usage in routes
@router.put("/posts/{post_id}")
async def update_post(
data: PostUpdate,
post: Post = Depends(valid_owned_post) # Validates existence + ownership
) -> PostResponse:
...
Async Patterns
Do
# Async DB with proper session handling
async def get_user(db: AsyncSession, user_id: int) -> User | None:
result = await db.execute(select(User).where(User.id == user_id))
return result.scalar_one_or_none()
# Concurrent independent calls
async def get_dashboard_data(user_id: int) -> DashboardData:
user, orders, notifications = await asyncio.gather(
user_service.get(user_id),
order_service.list_recent(user_id),
notification_service.get_unread(user_id),
return_exceptions=True
)
return DashboardData(user=user, orders=orders, notifications=notifications)
# Background tasks for non-blocking operations
@router.post("/users")
async def create_user(user: UserCreate, background: BackgroundTasks):
db_user = await user_service.create(user)
background.add_task(send_welcome_email, db_user.email)
background.add_task(analytics.track, "user_created", db_user.id)
return db_user
Don't
# WRONG: Blocking calls in async context
time.sleep(5) # Use: await asyncio.sleep(5)
requests.get(url) # Use: async with httpx.AsyncClient() as client
open("file").read() # Use: aiofiles.open()
# WRONG: Sequential when parallel is possible
user = await get_user(id)
orders = await get_orders(id) # Use asyncio.gather()
# WRONG: Sync dependencies in async routes
def get_db(): # Should be: async def get_db()
return SessionLocal()
Pydantic V2 Patterns
from pydantic import BaseModel, ConfigDict, Field, field_validator
from datetime import datetime
class BaseSchema(BaseModel):
"""Base for all schemas with common config."""
model_config = ConfigDict(
from_attributes=True,
str_strip_whitespace=True,
validate_assignment=True,
)
class UserCreate(BaseSchema):
email: str = Field(..., min_length=5, max_length=255)
password: str = Field(..., min_length=8)
@field_validator("email")
@classmethod
def normalize_email(cls, v: str) -> str:
return v.lower().strip()
class UserUpdate(BaseSchema):
model_config = ConfigDict(extra="forbid")
name: str | None = None
avatar_url: str | None = None
class UserResponse(BaseSchema):
id: int
email: str
name: str | None
created_at: datetime
# Never expose: password, is_admin, internal fields
class UserInDB(UserResponse):
hashed_password: str # Internal use only
Error Handling
# core/exceptions.py
from fastapi import Request
from fastapi.responses import JSONResponse
class AppException(Exception):
def __init__(self, message: str, code: str, status_code: int = 400):
self.message = message
self.code = code
self.status_code = status_code
class NotFoundError(AppException):
def __init__(self, resource: str, identifier: Any):
super().__init__(
message=f"{resource} with id '{identifier}' not found",
code="NOT_FOUND",
status_code=404
)
class AuthorizationError(AppException):
def __init__(self, message: str = "Not authorized"):
super().__init__(message=message, code="FORBIDDEN", status_code=403)
# Register handler
@app.exception_handler(AppException)
async def app_exception_handler(request: Request, exc: AppException):
return JSONResponse(
status_code=exc.status_code,
content={
"error": {
"code": exc.code,
"message": exc.message,
}
}
)
# Production: Hide stack traces
@app.exception_handler(Exception)
async def generic_exception_handler(request: Request, exc: Exception):
logger.exception("Unhandled exception", exc_info=exc)
return JSONResponse(
status_code=500,
content={"error": {"code": "INTERNAL_ERROR", "message": "Internal server error"}}
)
Security Essentials
See references/security-auth.md and references/security-owasp.md for complete patterns.
Quick Checklist
- Use PyJWT (not python-jose) for JWT handling
- Auth Code + PKCE for SPAs/Mobile (not password flow)
- Short-lived access tokens (15-30 min)
- Refresh tokens in HttpOnly cookies
- Rate limiting on auth endpoints
- Request body size limits
- pydantic-settings for secrets (never hardcode)
- Log sanitization (filter password, token, authorization)
Anti-Patterns
| Don't | Do |
|---|---|
| Business logic in routes | Move to services |
| DB queries in routes | Use service layer |
requests in async code | Use httpx.AsyncClient |
time.sleep() | Use asyncio.sleep() |
| Hardcoded config | Use pydantic-settings |
| Return dict from routes | Return Pydantic models |
| Skip type hints | Type everything |
| Global scoped_session | Request-scoped via Depends |
| Repository pattern overkill | Service + direct ORM |
| python-jose for JWT | Use PyJWT |
Scripts
scripts/scaffold_structure.py- Generate clean architecture foldersscripts/generate_migration.py- Alembic wrapper for async migrations
Assets
assets/docker-compose.yml- Postgres + Redis + API stackassets/Dockerfile- Multi-stage production build
Audit Checklist
When running /fastapi-audit, check:
-
Architecture
- Thin routes, fat services
- Consistent project structure
- No circular imports
-
Async
- No blocking calls in async code
- Proper session handling
- Concurrent calls where possible
-
Security (load
references/security-owasp.md)- Auth patterns correct
- Input validation complete
- No hardcoded secrets
-
Database (load
references/database-sqlalchemy.md)- Connection pooling configured
- N+1 queries prevented
- Migrations reversible
-
Observability (load
references/observability.md)- Structured logging
- Health checks present
- Metrics exposed
Source
git clone https://github.com/hackermanishackerman/claude-skills-vault/blob/main/.claude/skills/fastapi-senior-dev/SKILL.mdView on GitHub Overview
Turn FastAPI projects into production-grade backends by enforcing clean architecture, robust async patterns, and thorough audits. This skill covers scaffolding, architecture enforcement, DB integration, authentication, and security hardening for FastAPI codebases.
How This Skill Works
It emphasizes thin routes with fat services, a configuration-first approach using domain-scoped pydantic settings, and project organization aligned to project size (vertical slices by default). It also provides trigger-based workflows (/fastapi-init, /fastapi-structure, /fastapi-audit) and reference templates to guide database, security, and observability integrations.
When to Use It
- Scaffolding new FastAPI projects
- Implementing clean architecture patterns
- Database integration (PostgreSQL, MongoDB)
- Authentication (OAuth2, JWT, OIDC)
- Performance optimization & async patterns
Quick Start
- Step 1: Use /fastapi-init to scaffold a new FastAPI project with a clean architecture skeleton.
- Step 2: Run /fastapi-structure to analyze and reorganize an existing project into vertical slices or layers.
- Step 3: Run /fastapi-audit to review code for patterns, performance, and security; apply reference files as needed.
Best Practices
- Keep routes thin; move business logic into service layer.
- Use pydantic-settings and domain-based configuration for architectures.
- Choose a scalable project structure (vertical slice by feature).
- Adopt async patterns with async ORM/drivers and proper pooling.
- Incorporate security hardening and regular code audits (OWASP-aware).
Example Use Cases
- Scaffold a new FastAPI microservice with /fastapi-init and structure it with vertical slices.
- Audit an existing API using /fastapi-audit to identify performance and security gaps.
- Migrate to an async ORM for PostgreSQL and implement migrations in the scaffold.
- Implement OAuth2/JWT authentication across endpoints and protections.
- Restructure a monolith into modular services following the clean-architecture guidance.