의존성 주입을 이해했으니 이제 실제 저장소를 붙여 봅니다. SQLModel은 SQL 쿼리 문법을 자세히 몰라도 테이블을 파이썬 클래스로 표현할 수 있게 도와줍니다. SQLite는 파일 하나로 동작하는 가벼운 데이터 저장소라서 노트북에서도 바로 실습할 수 있습니다. FastAPI는 SQLAlchemy 계열 라이브러리를 모두 지원하지만, SQLModel이 입문자에게 가장 친절합니다.
준비: DB를 쉬운 말로 이해하기
- 데이터베이스(DB)는 "컴퓨터가 껐다 켜져도 남아 있는 표"라고 생각하면 됩니다.
- SQLModel은 "이 표가 어떤 모양인지"를 파이썬 코드로 적는 도구입니다.
- SQLite는 "이 표를 저장할 얇은 수첩" 정도로 보면 됩니다. 설치 없이 파일 하나로 끝납니다.
이 감각을 가지고 아래 실습을 따라가면 용어에 겁먹지 않고 진행할 수 있습니다.
이번 글에서 새로 나오는 용어
- SQLModel: Pydantic 모델과 SQLAlchemy 테이블을 한 번에 선언할 수 있는 라이브러리로, 이번 글에서 테이블과 JSON 스키마를 동시에 정의할 때 사용합니다.
- ORM(Object Relational Mapper): 파이썬 객체와 데이터베이스 테이블을 자동으로 변환해 주는 계층으로, SQL문을 직접 쓰지 않아도 CRUD를 수행하게 해 줍니다.
- SQLite: 파일 하나(
todo.db)만으로 동작하는 초경량 데이터베이스로, 서버 없이도 FastAPI와 영구 저장을 실습할 수 있는 저장소입니다. - Session: 데이터베이스 연결을 대표하는 SQLModel/SQLAlchemy 객체로, 트랜잭션을 열고
commit()으로 저장하는 흐름을 담당합니다. - 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_db와get_session을 작성하고/tasksCRUD를 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로 옮겨갈 때도 코드 변경이 최소화됩니다.
💬 댓글
이 글에 대한 의견을 남겨주세요