[FastAPI 시리즈 5편] 메모리 리스트로 CRUD 흐름 만들기

English version

Pydantic 모델을 배웠다면 작은 데이터 저장소를 두고 CRUD 흐름을 만들어 볼 수 있습니다. CRUD는 Create·Read·Update·Delete를 묶은 기본 데이터 조작 순서입니다. 어떤 앱이든 이 네 동작을 안정적으로 구현해야 다음 기능을 얹을 수 있습니다. 4편에서 정의한 모델과 검증 로직을 그대로 활용하면서, 이번 글에서는 데이터베이스 없이 리스트만으로 API 사이클을 반복 연습합니다.

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

  1. CRUD: 생성·조회·수정·삭제 네 동작을 묶은 말로, 글 전체에서 반복 실습하는 API 기본 루틴입니다.
  2. 상태 코드 201/204: 각각 “새로 만들었다”, “본문 없이 성공했다”를 뜻하는 HTTP 코드로, CRUD 동작마다 어떤 응답을 보내야 하는지 구분하게 해 줍니다.
  3. HTTPException: FastAPI가 즉시 특정 상태 코드와 메시지를 던질 수 있게 해 주는 도구로, 없는 ID 요청을 404로 처리할 때 사용합니다.
  4. 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 / curlPOST /tasks (201)GET /tasksGET /tasks/{id}PUT /tasks/{id}DELETE /tasks/{id} (204)tasks list (in memory) JSON bodyappend recordentire listsingle matchreplace entryremove entryHTTP response

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 순서를 계속 반복해 볼 수 있습니다.
  • 나중에 실제 데이터베이스로 옮길 때에도 함수 시그니처와 검증 구조는 그대로 재사용할 수 있습니다.

실습

  • 따라 하기: /tasks CRUD 경로를 모두 작성하고 Swagger UI로 POST→GET→PUT→DELETE 순서를 따라 실행한다.
  • 확장하기: status_code를 활용해 201, 204 응답을 정확히 반환하고, PATCH 경로를 추가해 본다.
  • 디버깅: 존재하지 않는 ID를 호출해 404 오류 메시지가 의도대로 나오는지 확인한다.
  • 완료 기준: CRUD 요청이 정상/에러 모두 예상한 상태 코드를 돌려주고 테스트 순서가 기록된다.

마무리

간단한 리스트 저장소라도 CRUD 흐름을 손에 익히는 데 충분합니다. 다음 글에서는 응답 모델과 검증 옵션을 더해 문서를 더욱 명확히 만들고, 사용자에게 일관된 응답 구조를 제공하는 방법을 살펴보겠습니다.

💬 댓글

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