Pydantic 모델을 배웠다면 작은 데이터 저장소를 두고 CRUD 흐름을 만들어 볼 수 있습니다. CRUD는 Create·Read·Update·Delete를 묶은 기본 데이터 조작 순서입니다. 어떤 앱이든 이 네 동작을 안정적으로 구현해야 다음 기능을 얹을 수 있습니다. 4편에서 정의한 모델과 검증 로직을 그대로 활용하면서, 이번 글에서는 데이터베이스 없이 리스트만으로 API 사이클을 반복 연습합니다.
이번 글에서 새로 나오는 용어
- CRUD: 생성·조회·수정·삭제 네 동작을 묶은 말로, 글 전체에서 반복 실습하는 API 기본 루틴입니다.
- 상태 코드 201/204: 각각 “새로 만들었다”, “본문 없이 성공했다”를 뜻하는 HTTP 코드로, CRUD 동작마다 어떤 응답을 보내야 하는지 구분하게 해 줍니다.
- HTTPException: FastAPI가 즉시 특정 상태 코드와 메시지를 던질 수 있게 해 주는 도구로, 없는 ID 요청을 404로 처리할 때 사용합니다.
- PATCH 메서드: 전체가 아닌 일부 필드를 바꿀 때 쓰는 HTTP 메서드로, 글 후반부 일정 관리 예제에서 상태만 빠르게 이동시키는 데 활용합니다.
실습 카드
- 예상 소요 시간: 45분
- 사전 준비: 4편 Pydantic 예제, Python 3.12,
curl또는 Swagger UI 사용 경험- 실습 목표: 리스트 기반 CRUD 엔드포인트를 만들고 상태 코드를 구분해 본다
이 글에서 할 것
- 전역 리스트를 간단한 저장소로 사용하기
- POST/GET/PUT/DELETE 엔드포인트 작성하기
- ID를 증가시키면서 항목을 찾고 갱신하기
준비 코드
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class Task(BaseModel):
title: str
done: bool = False
tasks: list[dict] = []
current_id = 0
tasks 리스트에는 실제 데이터를 딕셔너리 형태로 저장하고, current_id를 증가시키며 식별자를 부여합니다.
생성(Create)
@app.post("/tasks", 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
status_code=201로 생성 응답임을 명시합니다.model_dump()로 Pydantic 모델을 딕셔너리로 변환합니다.
조회(Read)
@app.get("/tasks")
def list_tasks():
return tasks
@app.get("/tasks/{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")
리스트 전체를 반환할 수도 있고, 특정 ID만 찾아서 응답할 수도 있습니다.
수정(Update)
@app.put("/tasks/{task_id}")
def update_task(task_id: int, task: Task):
for idx, t in enumerate(tasks):
if t["id"] == task_id:
updated = {"id": task_id, **task.model_dump()}
tasks[idx] = updated
return updated
raise HTTPException(status_code=404, detail="Task not found")
찾는 ID가 없으면 404를 던져 사용자에게 명확히 알려 줍니다.
삭제(Delete)
@app.delete("/tasks/{task_id}", status_code=204)
def delete_task(task_id: int):
for idx, t in enumerate(tasks):
if t["id"] == task_id:
tasks.pop(idx)
return
raise HTTPException(status_code=404, detail="Task not found")
삭제는 응답 본문이 필요 없으므로 204 상태 코드를 사용했습니다.
CRUD 요청/응답 흐름 한눈에 보기
Swagger UI든 curl이든 요청 흐름은 위와 같이 반복됩니다. 201/204 상태 코드를 달리하는 이유, 리스트 전체/특정 항목을 어떻게 가져오는지 등을 한 장짜리 다이어그램으로 정리해 두면 CRUD 학습이 눈에 잘 들어옵니다.
실전 예제: 프로젝트 일정 관리
동아리 협업 앱에서는 아래처럼 상태 필드를 추가해 간단한 칸반 보드를 흉내 낼 수 있습니다.
class ScheduleTask(Task):
status: str = "todo" # todo, doing, done
@app.patch("/schedule/{task_id}")
def move_task(task_id: int, status: str):
for task in tasks:
if task["id"] == task_id:
task["status"] = status
return task
raise HTTPException(status_code=404, detail="Task not found")
PATCH 메서드를 연습하면서 상태 변경만 빠르게 반영할 수 있고, 나중에 DB를 붙여도 동일한 로직을 유지하면 됩니다.
리스트 기반 연습의 의미
- DB 설정에 시간을 쓰지 않고 API 흐름 자체에 집중할 수 있습니다.
- Swagger UI에서 직접 CREATE → READ → UPDATE → DELETE 순서를 계속 반복해 볼 수 있습니다.
- 나중에 실제 데이터베이스로 옮길 때에도 함수 시그니처와 검증 구조는 그대로 재사용할 수 있습니다.
실습
- 따라 하기:
/tasksCRUD 경로를 모두 작성하고 Swagger UI로 POST→GET→PUT→DELETE 순서를 따라 실행한다. - 확장하기:
status_code를 활용해 201, 204 응답을 정확히 반환하고, PATCH 경로를 추가해 본다. - 디버깅: 존재하지 않는 ID를 호출해 404 오류 메시지가 의도대로 나오는지 확인한다.
- 완료 기준: CRUD 요청이 정상/에러 모두 예상한 상태 코드를 돌려주고 테스트 순서가 기록된다.
마무리
간단한 리스트 저장소라도 CRUD 흐름을 손에 익히는 데 충분합니다. 다음 글에서는 응답 모델과 검증 옵션을 더해 문서를 더욱 명확히 만들고, 사용자에게 일관된 응답 구조를 제공하는 방법을 살펴보겠습니다.
💬 댓글
이 글에 대한 의견을 남겨주세요