캡스톤디자인 프로젝트 GANADI(반려동물 안구질환 AI 스크리닝 플랫폼) 백엔드 개발에 처음으로 참여했다. 개발 자체도 처음이고 FastAPI도 처음이라 모르는 것투성이였는데, 첫날부터 팀원분께 버그를 잡아주시는 상황이 생겼다. 부끄럽지만 다음에 같은 실수를 반복하지 않기 위해 기록으로 남긴다.
name, role 필드 추가GET /api/diagnosis/history 전체 진단 이력 API 추가GET, PATCH /read-all, PATCH /{id})알림 API를 구현하면서 Notification 모델을 추가하고 아래 명령어를 실행했다.
alembic revision --autogenerate -m "add notifications table"
alembic upgrade head
언뜻 보면 정상적으로 실행된 것처럼 보였다. 하지만 팀원분께서 확인해보니 실제로 DB에 notifications 테이블이 생성되지 않았다.
alembic revision --autogenerate는 실제 DB에 연결해서 현재 DB 상태와 모델 코드를 비교해 차이점을 감지하는 명령어다.
현재 DB 상태: notifications 테이블 없음
모델 코드: Notification 클래스 있음
→ 차이 감지 → upgrade()에 create_table 코드 자동 생성
근데 나는 MySQL이 꺼져있는 상태에서 실행했다. 그러다보니:
MySQL 꺼져있음
→ alembic revision --autogenerate 실행
→ DB 연결 실패
→ DB랑 비교를 못함
→ upgrade() 함수가 비어있는 파일만 생성됨
→ alembic upgrade head 해도 아무것도 안됨
# ❌ 내가 만든 파일 (비어있음)
def upgrade():
pass
def downgrade():
pass
# ✅ 올바른 파일
def upgrade():
op.create_table('notifications',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('message', sa.String(500), nullable=False),
sa.Column('type', sa.String(50), nullable=False),
sa.Column('is_read', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
)
def downgrade():
op.drop_table('notifications')
1. MySQL 먼저 켜기 ← 핵심!
2. alembic revision --autogenerate -m "설명"
3. 생성된 파일 열어서 upgrade() 안에 코드 있는지 확인 ← 필수!
4. alembic upgrade head
upgrade() 함수가 비어있으면 MySQL이 꺼져있었던 것!
알림 전체 읽음 처리 API(PATCH /read-all)를 호출하면 422 에러가 발생했다.
FastAPI는 라우터를 위에서 아래로 순서대로 매칭한다.
# ❌ 내가 작성한 잘못된 순서
@router.patch("/{notification_id}") # 변수 경로 먼저
@router.patch("/read-all") # 고정 경로 나중에
/read-all 요청이 오면 FastAPI가 위에서부터 확인하는데, read-all이 {notification_id} 자리에 들어갈 수 있다고 판단해버린다. 그래서 /read-all 까지 못 가고 /{notification_id}에서 걸려 422 에러가 발생한 것이다.
# ✅ 올바른 순서
@router.patch("/read-all") # 고정 경로 먼저
@router.patch("/{notification_id}") # 변수 경로 나중에
고정 경로는 항상 변수 경로보다 위에 선언해야 한다!
| 메서드 | 용도 | 예시 |
|---|---|---|
| POST | 새로운 데이터 생성 | 회원가입, 알림 생성 |
| GET | 데이터 조회 | 알림 목록 조회 |
| PUT | 데이터 전체 수정 | 반려동물 정보 전체 수정 |
| PATCH | 데이터 일부 수정 | 알림 읽음 처리 (is_read만 변경) |
| DELETE | 데이터 삭제 | 반려동물 삭제 |
읽음 처리는 is_read 필드 하나만 False → True로 바꾸는 것이기 때문에 일부 수정인 PATCH가 적합하다. PUT을 쓰면 모든 필드를 다 보내야 하지만 PATCH는 변경할 필드만 보내면 된다.
개발도 처음, FastAPI도 처음인 상태에서 팀 프로젝트에 참여하다 보니 당연히 실수가 있을 수밖에 없다. 중요한 건 같은 실수를 반복하지 않는 것이라고 생각한다.
팀원분이 꼼꼼하게 버그를 잡아주셔서 오히려 덕분에 마이그레이션과 라우터 개념을 확실하게 이해할 수 있었다. 앞으로는 코드를 작성하고 나서 직접 눈으로 확인하는 습관을 들여야겠다.
MySQL 먼저 켜고, 마이그레이션 파일 열어서 확인하고, 고정 경로는 변수 경로보다 위에!
캡스톤디자인 프로젝트 - 2026.04.14