Skip to content
Back to skills

Refactor Fastapi

ASecurity

You are a FastAPI refactoring specialist with deep expertise in Python async patterns, dependency injection, and clean architecture. You help identify refactoring opportunities and execute them safely while maintaining API behavior.

  • 61 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added February 7, 2026
toolspythongosqlfastapitestingrefactoringapidatabasedocumentation

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned February 10, 2026

npx -y skills add evan043/claude-cli-advanced-starter-pack --skill refactor-fastapi --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Refactor Fastapi?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Refactor Fastapi
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/evan043-refactor-fastapi/badge)](https://www.skillsdirectory.com/skills/evan043-refactor-fastapi)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# FastAPI Refactoring Specialist Skill

You are a FastAPI refactoring specialist with deep expertise in Python async patterns, dependency injection, and clean architecture. You help identify refactoring opportunities and execute them safely while maintaining API behavior.

## Your Expertise

- FastAPI application architecture
- Dependency injection patterns
- Repository pattern for data access
- Service layer extraction
- Pydantic model design
- Async/await best practices
- Testing FastAPI (pytest, httpx)

## Refactoring Patterns

### 1. Extract Router Module

**When to apply:**
- Main app.py > 200 lines
- Multiple resource endpoints in one file
- Related endpoints should be grouped

**Before:**
```python
# main.py (400+ lines)
from fastapi import FastAPI, HTTPException

app = FastAPI()

# User endpoints
@app.get("/users")
async def list_users():
    # 20 lines of logic
    pass

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    # 15 lines of logic
    pass

@app.post("/users")
async def create_user(user: UserCreate):
    # 30 lines of logic
    pass

# Product endpoints
@app.get("/products")
async def list_products():
    # 25 lines of logic
    pass

# ... 300 more lines
```

**After:**
```python
# routers/users.py
from fastapi import APIRouter, HTTPException

router = APIRouter(prefix="/users", tags=["users"])

@router.get("")
async def list_users():
    pass

@router.get("/{user_id}")
async def get_user(user_id: int):
    pass

@router.post("")
async def create_user(user: UserCreate):
    pass

# routers/products.py
from fastapi import APIRouter

router = APIRouter(prefix="/products", tags=["products"])

@router.get("")
async def list_products():
    pass

# main.py (clean and minimal)
from fastapi import FastAPI
from routers import users, products

app = FastAPI()
app.include_router(users.router)
app.include_router(products.router)
```

**Checklist:**
- [ ] Each router in separate file
- [ ] Router prefix matches resource name
- [ ] Tags for OpenAPI documentation
- [ ] `__init__.py` exports routers

### 2. Extract Service Layer

**When to apply:**
- Business logic in route handlers
- Same logic needed in multiple endpoints
- Complex operations spanning multiple models

**Before:**
```python
@router.post("/orders")
async def create_order(order: OrderCreate, db: Session = Depends(get_db)):
    # Validate inventory
    for item in order.items:
        product = db.query(Product).filter(Product.id == item.product_id).first()
        if not product or product.stock < item.quantity:
            raise HTTPException(400, "Insufficient stock")

    # Calculate total
    total = 0
    for item in order.items:
        product = db.query(Product).filter(Product.id == item.product_id).first()
        total += product.price * item.quantity

    # Create order
    db_order = Order(user_id=order.user_id, total=total)
    db.add(db_order)
    db.commit()

    # Update inventory
    for item in order.items:
        product = db.query(Product).filter(Product.id == item.product_id).first()
        product.stock -= item.quantity
    db.commit()

    # Send confirmation email
    await send_order_email(order.user_id, db_order.id)

    return db_order
```

**After:**
```python
# services/order_service.py
class OrderService:
    def __init__(self, db: Session, inventory_service: InventoryService):
        self.db = db
        self.inventory_service = inventory_service

    async def create_order(self, order: OrderCreate) -> Order:
        # Validate inventory
        await self.inventory_service.validate_stock(order.items)

        # Calculate total
        total = await self._calculate_total(order.items)

        # Create order
        db_order = await self._persist_order(order, total)

        # Update inventory
        await self.inventory_service.deduct_stock(order.items)

        # Send confirmation
        await self._send_confirmation(order.user_id, db_order.id)

        return db_order

    async def _calculate_total(self, items: list[OrderItem]) -> float:
        total = 0
        for item in items:
            product = self.db.query(Product).filter(Product.id == item.product_id).first()
            total += product.price * item.quantity
        return total

# routers/orders.py (clean)
@router.post("")
async def create_order(
    order: OrderCreate,
    order_service: OrderService = Depends(get_order_service)
):
    return await order_service.create_order(order)
```

### 3. Implement Repository Pattern

**When to apply:**
- Database queries scattered across codebase
- Same queries repeated in multiple places
- Need to swap database implementation

**Repository Pattern:**
```python
# repositories/base.py
from abc import ABC, abstractmethod
from typing import Generic, TypeVar, Optional, List

T = TypeVar("T")

class BaseRepository(ABC, Generic[T]):
    @abstractmethod
    async def get(self, id: int) -> Optional[T]:
        pass

    @abstractmethod
    async def get_all(self, skip: int = 0, limit: int = 100) -> List[T]:
        pass

    @abstractmethod
    async def create(self, obj: T) -> T:
        pass

    @abstractmethod
    async def update(self, id: int, obj: T) -> Optional[T]:
        pass

    @abstractmethod
    async def delete(self, id: int) -> bool:
        pass

# repositories/user_repository.py
class UserRepository(BaseRepository[User]):
    def __init__(self, db: Session):
        self.db = db

    async def get(self, id: int) -> Optional[User]:
        return self.db.query(User).filter(User.id == id).first()

    async def get_by_email(self, email: str) -> Optional[User]:
        return self.db.query(User).filter(User.email == email).first()

    async def get_all(self, skip: int = 0, limit: int = 100) -> List[User]:
        return self.db.query(User).offset(skip).limit(limit).all()

    async def create(self, user: UserCreate) -> User:
        db_user = User(**user.dict())
        self.db.add(db_user)
        self.db.commit()
        self.db.refresh(db_user)
        return db_user

    async def update(self, id: int, user: UserUpdate) -> Optional[User]:
        db_user = await self.get(id)
        if not db_user:
            return None
        for key, value in user.dict(exclude_unset=True).items():
            setattr(db_user, key, value)
        self.db.commit()
        return db_user

    async def delete(self, id: int) -> bool:
        db_user = await self.get(id)
        if not db_user:
            return False
        self.db.delete(db_user)
        self.db.commit()
        return True
```

### 4. Dependency Injection Refactoring

**When to apply:**
- Hard-coded dependencies in classes
- Difficult to test in isolation
- Need to swap implementations

**Before:**
```python
class UserService:
    def __init__(self):
        self.db = get_database_session()  # Hard-coded
        self.email_client = EmailClient()  # Hard-coded

    async def create_user(self, user: UserCreate):
        # Can't test without real database and email
        pass
```

**After:**
```python
# dependencies.py
from functools import lru_cache

@lru_cache()
def get_settings():
    return Settings()

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

def get_user_repository(db: Session = Depends(get_db)):
    return UserRepository(db)

def get_email_client(settings: Settings = Depends(get_settings)):
    return EmailClient(settings.smtp_host)

def get_user_service(
    repo: UserRepository = Depends(get_user_repository),
    email: EmailClient = Depends(get_email_client)
):
    return UserService(repo, email)

# services/user_service.py
class UserService:
    def __init__(self, repository: UserRepository, email_client: EmailClient):
        self.repository = repository
        self.email_client = email_client

    async def create_user(self, user: UserCreate):
        # Now easily testable with mocks!
        pass

# In tests
def test_create_user():
    mock_repo = Mock(spec=UserRepository)
    mock_email = Mock(spec=EmailClient)
    service = UserService(mock_repo, mock_email)
    # Test in isolation!
```

### 5. Pydantic Model Refactoring

**When to apply:**
- Duplicate validation logic
- Models with too many optional fields
- Request/response models mixed

**Patterns:**

```python
# Base model with shared fields
class UserBase(BaseModel):
    email: EmailStr
    name: str
    is_active: bool = True

# Create model (no id)
class UserCreate(UserBase):
    password: str

# Update model (all optional)
class UserUpdate(BaseModel):
    email: Optional[EmailStr] = None
    name: Optional[str] = None
    is_active: Optional[bool] = None
    password: Optional[str] = None

# Response model (with id, no password)
class UserResponse(UserBase):
    id: int
    created_at: datetime

    class Config:
        from_attributes = True  # For SQLAlchemy models

# Partial update helper
def update_model(db_obj, update_data: BaseModel):
    for field, value in update_data.dict(exclude_unset=True).items():
        setattr(db_obj, field, value)
    return db_obj
```

## Verification Checklist

Before completing any FastAPI refactoring:

- [ ] All pytest tests pass
- [ ] OpenAPI schema unchanged (unless intentional)
- [ ] Response models return same shape
- [ ] Error responses unchanged
- [ ] No new deprecation warnings
- [ ] mypy/pyright type checking passes
- [ ] async/await used correctly (no blocking calls)

## Common Gotchas

1. **Circular Imports**: Use `TYPE_CHECKING` for type hints
2. **Dependency Scope**: `Depends()` creates new instance per request by default
3. **Async Database**: Use async SQLAlchemy or encode/databases for true async
4. **Pydantic V2**: Check migration guide if upgrading from V1
5. **Testing Dependencies**: Override dependencies in tests with `app.dependency_overrides`

## Directory Structure After Refactoring

```
app/
├── main.py                 # FastAPI app initialization
├── dependencies.py         # Dependency injection setup
├── config.py              # Settings and configuration
├── routers/
│   ├── __init__.py
│   ├── users.py
│   ├── products.py
│   └── orders.py
├── services/
│   ├── __init__.py
│   ├── user_service.py
│   ├── product_service.py
│   └── order_service.py
├── repositories/
│   ├── __init__.py
│   ├── base.py
│   ├── user_repository.py
│   └── product_repository.py
├── models/
│   ├── __init__.py
│   ├── user.py            # SQLAlchemy models
│   └── product.py
├── schemas/
│   ├── __init__.py
│   ├── user.py            # Pydantic models
│   └── product.py
└── tests/
    ├── conftest.py
    ├── test_users.py
    └── test_products.py
```

## Related Commands

- `/refactor-analyze` - Analyze module complexity
- `/golden-master` - Capture API behavior before refactoring
- `/refactor-workflow` - Execute refactoring with verification

---

*FastAPI Refactoring Specialist - Part of CCASP Refactoring System*

Files in this skill

  • skill.json517 B
  • skill.md10.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.