[FastAPI 시리즈 7편] APIRouter로 프로젝트 구조 정리하기

English version

엔드포인트가 늘어나면 main.py 하나로 관리하기 어렵습니다. APIRouter는 FastAPI에서 특정 경로 묶음을 모듈로 분리할 수 있게 해 주는 도구입니다. 라우터를 나누면 기능별로 파일을 관리하고 협업할 때 충돌을 줄일 수 있습니다. 6편까지 만들어 둔 사용자·작업 API를 조금 더 확장한다고 생각하고, 이번 글에서는 APIRouter로 파일을 나누고 모듈화를 쉽게 하는 방법을 실습합니다.

이번 글에서 새로 나오는 용어

  1. APIRouter: 특정 기능군의 경로들을 한 파일에 묶어 재사용하도록 돕는 FastAPI 도구로, 이번 글의 구조화 핵심입니다.
  2. include_router: 분리한 라우터를 메인 앱에 합치는 함수로, 여러 기능 모듈을 한 FastAPI 인스턴스에 붙일 때 사용합니다.
  3. prefix: 모든 경로 앞에 자동으로 붙는 문자열(/tasks 등)로, 라우터 단위로 공통 URL을 지정해 코드 중복을 줄입니다.
  4. tags: Swagger UI에서 카테고리처럼 보이는 라벨로, 라우터마다 지정하면 문서에서 기능을 빠르게 찾을 수 있습니다.
  5. 의존성(dependencies): 라우터 전체에 인증·검증 함수를 한 번에 적용할 수 있는 설정으로, 관리자 라우터 예제에서 활용합니다.

실습 카드

  • 예상 소요 시간: 40분
  • 사전 준비: 6편 프로젝트 구조, 기본 Python 패키지 이해
  • 실습 목표: APIRouter를 분리하고 include_router로 메인 앱에 합친다

이 글에서 할 것

  • app/api 폴더와 라우터 파일 만들기
  • APIRouter에 경로를 등록하고 include_router로 합치기
  • 공통 prefix와 tag를 선언해 문서를 정리하기

폴더 구조 예시

my-fastapi-app/
├─ app/
│  ├─ __init__.py
│  ├─ main.py
│  └─ api/
│     ├─ __init__.py
│     └─ tasks.py
└─ pyproject.toml

app/main.py는 FastAPI 인스턴스를 만들고 라우터를 등록하는 역할만 담당합니다.

라우터 정의

app/api/tasks.py 파일:

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel

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

class Task(BaseModel):
    title: str
    done: bool = False

tasks: list[dict] = []
current_id = 0

@router.post("", status_code=201)
def create_task(task: Task):
    global current_id
    current_id += 1
    record = {"id": current_id, **task.model_dump()}
    tasks.append(record)
    return record

@router.get("")
def list_tasks():
    return tasks

@router.get("/{task_id}")
def get_task(task_id: int):
    for t in tasks:
        if t["id"] == task_id:
            return t
    raise HTTPException(status_code=404, detail="Task not found")

prefix="/tasks" 덕분에 라우트마다 다시 경로를 적을 필요가 없습니다. Swagger UI에서도 tasks 태그 아래로 묶입니다.

메인 파일에서 합치기

app/main.py:

from fastapi import FastAPI
from app.api import tasks

app = FastAPI()

app.include_router(tasks.router)

@app.get("/health")
def health_check():
    return {"status": "ok"}

include_router를 여러 번 호출하면 도메인별 라우터를 계속 추가할 수 있습니다. 설정을 묶어야 하면 APIRouterdependenciesresponses를 지정해 공통 규칙을 적용할 수도 있습니다.

실전 예제: 관리자 라우터 추가

학생용 API와 관리자용 API를 구분해야 한다면 아래처럼 라우터를 두 개로 나누어 등록합니다.

from app.api import tasks, admin

app.include_router(tasks.router)
app.include_router(admin.router, prefix="/admin", tags=["admin"], dependencies=[Depends(check_admin)])

관리자 라우터에는 공통 의존성을 묶어 두고, 문서에서도 별도 태그로 구분하여 협업자들이 필요한 엔드포인트를 빠르게 찾을 수 있습니다.

uv 명령과 임포트 경로

폴더 구조가 바뀌었을 때도 실행 명령은 동일합니다. 다만 uv run fastapi dev app/main.py처럼 경로만 바르게 지정하면 됩니다. uvicorn을 직접 실행할 때는 uv run uvicorn app.main:app --reload 형태를 사용합니다.

구조를 나눌 때 챙길 것

  • 모듈 간 순환 참조를 피하기 위해 데이터 모델과 라우터를 적절히 분리합니다.
  • 대규모 프로젝트가 될수록 core, schemas, services 같은 패키지를 만들어 관심사를 분리하면 좋습니다.
  • 라우터마다 tags를 지정하면 문서 페이지에서 찾기가 쉬워집니다.

실습

  • 따라 하기: app/api/tasks.pyAPIRouter를 만들고 app/main.py에서 include_router로 등록한다.
  • 확장하기: 관리자 라우터를 추가하고 별도 prefix/tag를 지정해 /docs에서 구분되는지 확인한다.
  • 디버깅: 잘못된 임포트 경로나 순환 참조로 인해 실행이 안 되면 구조를 재배치해 해결한다.
  • 완료 기준: uv run fastapi dev app/main.py 실행 후 /docs에 두 개 이상의 태그가 보인다.

마무리

APIRouter는 FastAPI 프로젝트를 정리하는 기본 도구입니다. 파일을 나누고 prefix, tags를 선언하는 것만으로도 구조가 훨씬 선명해집니다. 다음 글에서는 지금까지 만든 내용을 바탕으로 테스트나 배포 흐름을 어떻게 준비할지 살펴볼 예정입니다.

💬 댓글

이 글에 대한 의견을 남겨주세요