[FastAPI 시리즈 9편] SQLModel로 데이터베이스 첫걸음

English version

의존성 주입을 이해했으니 이제 실제 저장소를 붙여 봅니다. SQLModel은 SQL 쿼리 문법을 자세히 몰라도 테이블을 파이썬 클래스로 표현할 수 있게 도와줍니다. SQLite는 파일 하나로 동작하는 가벼운 데이터 저장소라서 노트북에서도 바로 실습할 수 있습니다. FastAPI는 SQLAlchemy 계열 라이브러리를 모두 지원하지만, SQLModel이 입문자에게 가장 친절합니다.

준비: DB를 쉬운 말로 이해하기

  • 데이터베이스(DB)는 "컴퓨터가 껐다 켜져도 남아 있는 표"라고 생각하면 됩니다.
  • SQLModel은 "이 표가 어떤 모양인지"를 파이썬 코드로 적는 도구입니다.
  • SQLite는 "이 표를 저장할 얇은 수첩" 정도로 보면 됩니다. 설치 없이 파일 하나로 끝납니다.

이 감각을 가지고 아래 실습을 따라가면 용어에 겁먹지 않고 진행할 수 있습니다.

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

  1. SQLModel: Pydantic 모델과 SQLAlchemy 테이블을 한 번에 선언할 수 있는 라이브러리로, 이번 글에서 테이블과 JSON 스키마를 동시에 정의할 때 사용합니다.
  2. ORM(Object Relational Mapper): 파이썬 객체와 데이터베이스 테이블을 자동으로 변환해 주는 계층으로, SQL문을 직접 쓰지 않아도 CRUD를 수행하게 해 줍니다.
  3. SQLite: 파일 하나(todo.db)만으로 동작하는 초경량 데이터베이스로, 서버 없이도 FastAPI와 영구 저장을 실습할 수 있는 저장소입니다.
  4. Session: 데이터베이스 연결을 대표하는 SQLModel/SQLAlchemy 객체로, 트랜잭션을 열고 commit()으로 저장하는 흐름을 담당합니다.
  5. create_engine: 데이터베이스 URL을 기반으로 실제 연결 풀을 만드는 함수로, sqlite:///./todo.db 같은 주소를 넣어 FastAPI 앱과 DB를 잇습니다.

실습 카드

  • 예상 소요 시간: 60분
  • 사전 준비: 8편 의존성 코드, Python 3.12, SQLite 설치(내장)
  • 실습 목표: SQLModel로 테이블을 만들고 CRUD 엔드포인트를 DB에 연결한다

이 글에서 할 것

  • SQLModel과 SQLite 조합이 왜 입문에 적합한지 확인합니다.
  • init_db, get_session을 작성해 FastAPI와 DB를 연결합니다.
  • CRUD 엔드포인트를 세션에 연결해 실제 파일에 데이터를 저장합니다.

왜 SQLModel인가

SQLModel은 Pydantic 모델을 그대로 테이블로도 쓰게 해 줍니다. 별도의 ORM 문법을 추가로 익히지 않아도 되고, 타입 힌트가 그대로 문서로 이어집니다. SQLite는 노트북에 기본 포함된 파일형 데이터베이스라서 서버 없이도 데이터를 영구 보관하며 연습할 수 있습니다. 이 조합이면 고등학생도 노트북 하나로 FastAPI·DB 흐름을 동시에 익힐 수 있습니다.

설치와 초기 세팅

uv add sqlmodel sqlalchemy sqlite-utils

SQLite 파일 데이터베이스를 기본으로 사용하겠습니다. app/db.py에 엔진과 세션 헬퍼를 만듭니다.

from sqlmodel import SQLModel, Session, create_engine

DATABASE_URL = "sqlite:///./todo.db"
engine = create_engine(DATABASE_URL, echo=True)

def init_db():
    SQLModel.metadata.create_all(engine)

def get_session():
    with Session(engine) as session:
        yield session

init_db()는 애플리케이션 시작 시 한 번 호출하면 됩니다. 이 단계만 통과하면 JSON 응답에 쓰던 리스트 대신 진짜 파일에 데이터가 쌓입니다.

모델 정의

from typing import Optional
from sqlmodel import SQLModel, Field

class Task(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    title: str
    done: bool = False

table=True로 선언하면 SQLAlchemy 테이블과 Pydantic 모델을 동시에 생성합니다. 라우터에서는 동일한 클래스를 요청/응답 모델로 재사용할 수 있습니다. 즉, 코드 한 번만 작성해도 API와 DB가 같은 모양을 공유하는 셈입니다.

CRUD 엔드포인트 연결

from fastapi import APIRouter, Depends, HTTPException
from sqlmodel import select

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

@router.post("", response_model=Task, status_code=201)
def create_task(task: Task, session: Session = Depends(get_session)):
    session.add(task)
    session.commit()
    session.refresh(task)
    return task

@router.get("", response_model=list[Task])
def list_tasks(session: Session = Depends(get_session)):
    return session.exec(select(Task)).all()

@router.patch("/{task_id}")
def toggle_task(task_id: int, session: Session = Depends(get_session)):
    task = session.get(Task, task_id)
    if not task:
        raise HTTPException(status_code=404)
    task.done = not task.done
    session.add(task)
    session.commit()
    session.refresh(task)
    return task

의존성 덕분에 세션을 주입받는 것만으로 데이터베이스 접근이 가능해졌습니다. "이 함수는 DB가 필요해요"라고 선언하면 FastAPI가 준비를 대신해 주니, 비즈니스 로직에 집중할 수 있습니다.

(선택) 마이그레이션과 확장 고려

  • SQLite는 개발과 단일 인스턴스 배포에 적합하지만, 다중 인스턴스 환경에서는 PostgreSQL 같은 서버형 DB로 이전하는 편이 좋습니다.
  • SQLModel은 SQLAlchemy 코어를 그대로 사용하므로 Alembic 기반 마이그레이션 도구를 그대로 연동할 수 있습니다.
  • 모델이 많아지면 schemas.py, models.py, repositories.py로 나누어 읽기/쓰기 책임을 분리하면 테스트가 쉬워집니다.

왜 중요한가

API가 리스트에만 의존하면 서버를 재시작할 때마다 데이터가 사라집니다. SQLModel과 SQLite를 붙이면 같은 FastAPI 코드가 실제 파일에 기록을 남기고, 여러 요청이 동시에 와도 데이터를 잃지 않습니다. 이 흐름을 익힌 상태라면 이후 PostgreSQL 같은 서버형 DB로 넘어갈 때 구조적 차이만 집중해서 배우면 되므로 학습 피로가 크게 줄어듭니다. 고등학생 실습에서도 "추가 → 새로 고침해도 남아 있음"이라는 경험이 가장 큰 동기부여가 됩니다.

실습

  • 따라 하기: init_dbget_session을 작성하고 /tasks CRUD를 SQLModel 테이블에 연결한다.
  • 확장하기: PATCH/필터 등 추가 라우트를 만들어 SQL 쿼리를 직접 작성해 본다.
  • 디버깅: sqlite3 todo.db .tables로 테이블 생성 여부를 확인하고, 트랜잭션 누락으로 데이터가 저장되지 않을 때 COMMIT 흐름을 점검한다.
  • 완료 기준: POST 요청 후 todo.db에 데이터가 저장되고, GET/PATCH가 동일 데이터를 다룬다.

실행 확인

uv run fastapi dev app/main.py

/docs에서 POST 요청으로 할 일을 추가하면 todo.db 파일이 생성되고 데이터가 저장됩니다. 10편에서는 이 데이터베이스를 인증/권한 흐름과 연결해 사용자별 데이터를 보호하는 기초를 다집니다.

마무리

SQLModel과 SQLite를 연결하면 복잡한 설정 없이도 지속 가능한 저장소를 확보할 수 있습니다. 지금 구조를 그대로 확장하면 PostgreSQL 같은 서버형 DB로 옮겨갈 때도 코드 변경이 최소화됩니다.

💬 댓글

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