소프트웨어 개발/백엔드

📚[FastAPI] 2장. FastAPI 기본 구조 설계: 디렉토리 구성과 라우팅 전략

브라더댄 2025. 1. 26. 13:54
728x90
반응형
SMALL

안녕하세요! 이번 2장에서는 FastAPI 프로젝트를 구성할 때 어떤 식으로 폴더(디렉토리)를 구성하고, 엔드포인트를 모듈화하여 라우팅을 체계적으로 진행할지 자세히 알아보겠습니다. 프로젝트 구조는 유지보수성과 확장성에 직접적인 영향을 주므로, 미리 견고한 틀을 잡아두는 것이 매우 중요합니다.

아래에서는 실제 운영 환경에서 자주 사용하는 방식들을 토대로 예시 구조를 제시하고, 라우팅과 모듈화 전략을 구체적인 코드 예시와 함께 살펴보겠습니다.


2.1. FastAPI 프로젝트 핵심 개념

2.1.1. FastAPI의 주요 컴포넌트

  1. FastAPI() 객체
    • 프레임워크의 핵심으로, 앱 전역 설정이나 이벤트 훅 등을 처리
    • @app.get(), @app.post() 데코레이터를 통해 라우트를 직접 정의할 수도 있지만, 규모가 커지면 라우터(routers)로 분리하는 것이 일반적
  2. 라우트 데코레이터
    • @app.get("/users"), @app.post("/items") 등으로 간단히 경로와 메서드를 지정
    • 규모가 커질수록, APIRouter를 활용해 엔드포인트를 모듈별로 나누는 것이 권장됨
  3. Dependency Injection
    • **Depends()**를 이용해 DB 세션, 인증 정보, 환경 설정 등을 깔끔하게 주입할 수 있음
    • 코드의 재사용성과 테스트 편의성을 높여주는 핵심 기능
  4. Pydantic 스키마
    • 모델(BaseModel)을 통해 입력/출력 형식을 선언적(Declarative)으로 정의
    • 요청 바디나 쿼리 파라미터를 자동 검증하고, API 문서화에도 도움을 줌

2.1.2. 왜 디렉토리 구조가 중요한가?

  • 유지보수성: 파일이 한 곳에 몰려 있으면, 변경 사항을 파악하기 어렵고 충돌이 잦아짐
  • 확장성: 코드가 커질수록 라우트, 모델, 스키마, 비즈니스 로직 등을 분리해둬야 확장에 유리
  • 협업: 팀원들이 코드 위치나 역할을 쉽게 찾고, 충돌을 줄이는 데 도움이 됨

2.2. 프로젝트 디렉토리 구조 설계

아래는 실무에서 많이 쓰이는 예시 구조입니다. 필요에 따라 조금씩 변형할 수 있지만, 핵심 컨셉은 기능별/역할별 분리입니다.

app/
├── main.py                # FastAPI 애플리케이션 엔트리포인트
├── core/                  # 설정, 보안, 유틸성 모듈
│   ├── config.py          # 환경 변수 로드, 전역 설정
│   └── security.py        # 인증, JWT 로직 등
├── db/
│   ├── base.py            # Base = declarative_base() 등
│   ├── session.py         # DB 연결 엔진, 세션 생성
│   └── migrations/        # Alembic 마이그레이션 폴더
├── models/                # SQLAlchemy 모델 정의
│   ├── user.py
│   ├── item.py
│   └── ...
├── schemas/               # Pydantic 스키마
│   ├── user.py
│   ├── item.py
│   └── ...
├── crud/                  # DB 처리 로직 (Create, Read, Update, Delete)
│   ├── user.py
│   ├── item.py
│   └── ...
├── api/
│   └── v1/                # 버전별 API (v1, v2 등)
│       ├── endpoints/     # 실제 라우트(엔드포인트)들을 모아둔 디렉토리
│       │   ├── user.py
│       │   ├── item.py
│       │   └── ...
│       └── routers.py     # v1 라우터들을 모아 FastAPI에 등록하는 모듈
├── tests/                 # 테스트 코드
│   ├── test_user.py
│   ├── test_item.py
│   └── ...
└── celery_app.py          # Celery 초기화 (비동기 작업 필요 시)
  • main.py: app = FastAPI() 인스턴스를 생성하고, 필요한 라우터를 불러와 등록
  • core/: 공통 설정, 보안 관련 로직, 인증 헬퍼 함수 등
  • db/: DB 연결, 세션, 마이그레이션(Alembic) 관련
  • models/: SQLAlchemy ORM 모델
  • schemas/: Pydantic 데이터 검증/직렬화 모델
  • crud/: DB 액세스 로직(ORM 사용), 반복적인 CRUD 코드를 깔끔하게 캡슐화
  • api/: FastAPI 라우팅 코드, 엔드포인트들(Controller) 집합
  • tests/: pytest 기반 테스트

2.3. 라우팅 전략과 APIRouter 사용

2.3.1. APIRouter란?

  • FastAPI는 규모가 커질 경우, 엔트리포인트 하나(main.py)에 모든 라우트를 정의하기보다는 APIRouter를 사용해 여러 파일로 분리하기를 권장합니다.
  • **fastapi.APIRouter**를 통해 엔드포인트(함수)들을 그룹핑하고, **app.include_router()**로 메인 앱에 연결할 수 있습니다.

기본 예시

# app/api/v1/endpoints/user.py
from fastapi import APIRouter

router = APIRouter()

@router.get("/users")
def read_users():
    return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]

@router.post("/users")
def create_user(name: str):
    return {"id": 3, "name": name}

이렇게 router = APIRouter()를 만든 뒤, @router.get(), @router.post() 등을 달아줍니다.

2.3.2. 라우트 등록 (routers.py 예시)

# app/api/v1/routers.py
from fastapi import APIRouter
from app.api.v1.endpoints import user, item

api_router = APIRouter()
api_router.include_router(user.router, prefix="/users", tags=["users"])
api_router.include_router(item.router, prefix="/items", tags=["items"])
  • 여러 라우터(user.router, item.router)를 하나의 api_router로 묶습니다.
  • prefix="/users": 해당 라우터 안의 모든 엔드포인트가 /users 경로를 가지도록 지정
  • tags=["users"]: Swagger UI 문서화 시 “users”라는 구분 섹션을 생성

2.3.3. 메인 앱에 라우터 연결 (main.py)

# app/main.py
from fastapi import FastAPI
from app.api.v1.routers import api_router

app = FastAPI(
    title="My FastAPI Project",
    description="API documentation",
    version="1.0.0",
)

app.include_router(api_router, prefix="/api/v1")
  • 이제 GET /api/v1/users, POST /api/v1/items 등의 엔드포인트가 자동 등록됩니다.
  • /docs(Swagger UI)나 /redoc에 들어가면 라우트가 깔끔히 정리되어 보입니다.

2.4. 예시 코드: User 엔드포인트

좀 더 구체적인 예시를 들어보겠습니다. 사용자(User) 관련 API를 작성한다고 가정합시다.

2.4.1. user.py (엔드포인트 파일)

# app/api/v1/endpoints/user.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List

from app.schemas.user import UserCreate, UserRead
from app.crud.user import create_user, get_users
from app.api.deps import get_db

router = APIRouter()

@router.post("/", response_model=UserRead, status_code=201)
def create_new_user(user_in: UserCreate, db: Session = Depends(get_db)):
    # 이미 존재하는 유저인지 확인
    existing_user = db.query(...).filter(...).first()
    if existing_user:
        raise HTTPException(status_code=400, detail="User already registered")
    
    return create_user(db, user_in)

@router.get("/", response_model=List[UserRead])
def read_users(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
    users = get_users(db, skip=skip, limit=limit)
    return users
  • @router.post("/"): /users 경로(상위 prefix가 /users)에 POST 요청을 처리
  • response_model=UserRead: 응답을 Pydantic 모델 UserRead 형태로 직렬화
  • Depends(get_db): DB 세션 의존성을 주입받아 CRUD 함수에 넘김

2.4.2. crud/user.py (DB 로직)

# app/crud/user.py
from sqlalchemy.orm import Session
from app.models.user import User
from app.schemas.user import UserCreate

def create_user(db: Session, user_in: UserCreate) -> User:
    db_user = User(
        email=user_in.email,
        hashed_password=user_in.hashed_password,
        # 기타 필드
    )
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    return db_user

def get_users(db: Session, skip: int = 0, limit: int = 10) -> list[User]:
    return db.query(User).offset(skip).limit(limit).all()
  • 컨트롤러 로직(라우트)와 DB 로직(CRUD)을 분리해두면, 테스트와 유지보수에 용이
  • 대형 프로젝트에서 CRUD가 늘어날 수록 모듈별 정리가 꼭 필요

2.4.3. schemas/user.py (Pydantic 스키마)

# app/schemas/user.py
from pydantic import BaseModel, EmailStr

class UserBase(BaseModel):
    email: EmailStr

class UserCreate(UserBase):
    hashed_password: str

class UserRead(UserBase):
    id: int

    class Config:
        orm_mode = True
  • UserBase: 공통 필드 email
  • UserCreate: 회원가입 시 필요한 필드 (hashed_password 실제론 bcrypt 해시 등)
  • UserRead: 클라이언트 응답 시 표시할 필드. id를 포함, orm_mode = True로 SQLAlchemy 객체 자동 변환

2.5. 라우팅 베스트 프랙티스

  1. URL 명명 규칙
    • GET /api/v1/users → 사용자 목록 조회
    • GET /api/v1/users/{user_id} → 특정 사용자 조회
    • POST /api/v1/users → 사용자 생성
    • PUT /api/v1/users/{user_id} or PATCH /api/v1/users/{user_id} → 사용자 수정
    • DELETE /api/v1/users/{user_id} → 사용자 삭제
    • 직관적으로 리소스(Resource)와 동작(CRUD)을 나타내도록 설계
  2. 계층적 라우트/네임스페이스
    • @router.get("/{user_id}/items"): 특정 사용자 소유의 아이템을 조회
    • api/v1/endpoints 디렉토리 내에 기능별 파일(user.py, item.py, auth.py 등)을 나누어 코드를 분산
  3. 메서드와 응답 코드 일관성
    • 생성은 POST + 201 Created, 조회는 GET + 200 OK, 삭제는 DELETE + 204 No Content(또는 200 OK), 등 REST 규칙을 지키면 협업 시 혼동이 줄어듦
  4. API 버전 관리
    • 라우터가 늘어날 때, api/v1, api/v2 처럼 버전을 라우트에 명시해두면 안전하게 버전별 호환성을 유지 가능

2.6. OpenAPI 문서 자동화

2.6.1. Swagger UI & Redoc

  • FastAPI는 /docs 경로에서 Swagger UI, /redoc 경로에서 ReDoc 문서 페이지를 자동 제공
  • response_model, status_code, Pydantic 스키마를 활용하면, API 문서가 자동으로 풍부해집니다.

2.6.2. 문서 커스터마이징

app = FastAPI(
    title="My Awesome API",
    description="설명 문구를 적을 수 있습니다.",
    version="1.0.0",
    openapi_url="/openapi.json",   # OpenAPI 스펙 문서 경로
    docs_url="/docs",             # Swagger UI 경로
    redoc_url="/redoc",           # ReDoc 경로
)
  • title, description, version 등을 설정해 API 문서를 좀 더 알차게 꾸밀 수 있습니다.
  • redoc_url=None처럼 특정 문서 URL을 비활성화할 수도 있습니다.

2.7. 실제 활용 예시

2.7.1. 전자상거래(E-Commerce) 백엔드

  • api/v1/endpoints/product.py, order.py, payment.py 등으로 기능을 분리
  • /api/v1/products, /api/v1/orders 등 리소스 기반 라우트 설계
  • 관리자(Admin)와 일반 사용자(User) 라우트를 구분하거나, depends_on을 통해 권한 제어

2.7.2. 블로그/게시판 서비스

  • api/v1/endpoints/post.py, comment.py, auth.py 등
  • 게시글, 댓글, 좋아요(Like) 기능을 각각 라우트로 정의
  • Pydantic 스키마를 활용해 게시글/댓글의 길이 제한, 필수 필드 등을 강제

2.7.3. 사내 ERP(Enterprise Resource Planning)

  • 모듈별(인사, 재무, 영업, 재고 등)로 라우트/CRUD를 완전히 분리
  • 라우트가 매우 많아질 수 있으므로, endpoints 디렉토리를 세분화(hr/employee.py, hr/attendance.py 등)
  • API 문서화로 사내 다른 팀/시스템과 협업이 수월해짐

추가 팁

  • router prefixinclude_router를 활용해 URL 경로를 잘 구조화하세요.
  • API 보안을 위해서는 Depends()를 이용한 JWT 인증이나 OAuth2PasswordBearer를 적절히 삽입해, 인증이 필요한 라우트만 보호할 수 있습니다.
  • 라우트를 작성할 때 예외 처리를 꼼꼼히 하고, 코드 리뷰 시 라우트가 너무 길어지지 않도록 적절히 분리하세요.

2.8. 마무리

이번 2장에서는 FastAPI 프로젝트를 어떻게 구조화하면 좋을지, 그리고 라우터(APIRouter)를 활용해 엔드포인트를 모듈화하는 전략에 대해 구체적으로 살펴봤습니다.

  1. 디렉토리 구조: main.py, core/, db/, models/, schemas/, crud/, api/, tests/ 등 역할별로 분리
  2. APIRouter 사용: include_router로 여러 엔드포인트를 일괄 등록, prefix·tags를 통해 URL과 문서화를 체계적으로 관리
  3. 예시 코드: User 엔드포인트, CRUD 레이어, Pydantic 스키마를 연계해 작동 원리를 이해
  4. OpenAPI 문서화: FastAPI가 자동 제공하는 /docs, /redoc을 통해 편리하게 API 문서 확인

다음 장(3장)에서는 데이터베이스 연동(SQLAlchemy, Alembic)과 CRUD 구현에 초점을 맞추어, 실제 DB에 데이터를 저장하고 버전 관리(마이그레이션)까지 어떻게 처리하는지 알아볼 예정입니다. 디렉토리 구조를 잘 잡아둔 만큼, DB 연동 로직도 수월하게 정리할 수 있을 것입니다.

 

728x90
반응형
LIST