FastAPI를 사용하면서 간단한 API 업데이트 작업을 진행하던 중, 예상치 못한 500 Internal Server Error를 마주하게 되었습니다. 로그를 살펴보니 bson.errors.InvalidId 오류가 원인이었습니다. 이 글에서는 해당 문제의 원인과 해결 방법을 공유합니다.
FastAPI에서 다음과 같은 두 개의 PATCH 메서드를 정의하고 있었습니다.
@app.patch("/news/update/{item_id}")
async def update_news_item(item_id: str):
...
@app.patch("/news/update/all")
async def update_all_news_items():
...
이 상태에서 /news/update/all로 PATCH 요청을 보내면 다음과 같은 오류가 발생했습니다:
bson.errors.InvalidId: 'all' is not a valid ObjectId, it must be a 12-byte input or a 24-character hex string
FastAPI는 라우팅 시 URL 패턴을 위에서부터 아래로 순차적으로 검사합니다. 따라서 /news/update/{item_id}가 먼저 정의되어 있으면, /news/update/all 요청도 {item_id} = 'all'로 인식됩니다. 이 값은 MongoDB의 ObjectId로 변환될 수 없는 문자열이기 때문에 오류가 발생한 것입니다.
즉, FastAPI가 "all"이라는 문자열을 동적으로 받아들일 수 있는 item_id 값으로 착각한 것이죠.
문제는 간단하게 라우트 순서를 변경하는 것만으로 해결할 수 있었습니다.
@app.patch("/news/update/all")
async def update_all_news_items():
...
@app.patch("/news/update/{item_id}")
async def update_news_item(item_id: str):
...
/news/update/all과 같이 고정된 문자열이 들어가는 경로는, 동적 파라미터 (/{item_id}) 경로보다 먼저 정의해야 FastAPI가 정확하게 매칭할 수 있습니다.
/all)는 가변 경로 (/{item_id})보다 위쪽에 선언해야 합니다.ObjectId로 변환할 수 없는 값이 들어오면 bson.errors.InvalidId 예외가 발생합니다.FastAPI처럼 선언형 라우팅을 사용하는 프레임워크에서는 라우팅 순서도 코드의 일부분이라는 사실을 다시 한 번 느낄 수 있었던 경험이었습니다. 앞으로 유사한 API 구조를 설계할 때 고정 경로와 동적 경로의 위치를 더욱 신경 써야겠습니다.