Skip to content

Architecture

Project structure and design patterns used in the FastAPI Boilerplate.

Project Structure

Directory Layout

fastapi_boilerplate/
├── app/
│   ├── api/
│   │   └── v1/
│   │       ├── controllers/
│   │       └── api.py
│   ├── controllers/
│   ├── core/
│   │   ├── config.py
│   │   ├── database.py
│   │   └── security.py
│   ├── models/
│   ├── schemas/
│   └── services/
├── alembic/
│   └── versions/
├── docs/
├── tests/
└── .env

Design Patterns

Repository Pattern

The repository pattern is used to abstract the data layer, making it easier to swap out implementations or test the application.

class UserRepository:
    def __init__(self, db: Session):
        self.db = db

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

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

Service Layer

The service layer contains business logic and orchestrates operations between repositories and other services.

class UserService:
    def __init__(self, user_repository: UserRepository):
        self.user_repository = user_repository

    async def create_user(self, user_data: UserCreate) -> User:
        if await self.user_repository.get_by_email(user_data.email):
            raise HTTPException(
                status_code=400,
                detail="Email already registered"
            )
        return await self.user_repository.create(user_data)

Dependency Injection

FastAPI Dependencies

FastAPI's dependency injection system is used to manage dependencies and provide them to route handlers.

async def get_db() -> Session:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@router.post("/users/", response_model=UserResponse)
async def create_user(
    user: UserCreate,
    db: Session = Depends(get_db),
    user_service: UserService = Depends()
):
    return await user_service.create_user(user)

Error Handling

Custom Exception Handler

from fastapi import Request, status
from fastapi.responses import JSONResponse
from app.core.exceptions import CustomException

@app.exception_handler(CustomException)
async def custom_exception_handler(request: Request, exc: CustomException):
    return JSONResponse(
        status_code=exc.status_code,
        content={
            "detail": exc.detail,
            "error_code": exc.error_code
        }
    )