반응형

파이썬 FastAPI 데이터베이스 연동
: SQLAlchemy ORM 시작

여러분, 웹 API를 만들었는데 데이터를 저장할 방법이 없어서 매번 초기화된다고요?
그렇다면 지금이 바로 ORM을 배워야 할 순간입니다!

 

 

안녕하세요, 여러분! 😊

FastAPI로 REST API를 구현하면서 데이터를 메모리나 리스트에만 저장했다면, 이제 다음 단계로 나아갈 차례입니다.

바로 데이터베이스 연동이죠.

특히 실습 환경에서는 간편한 SQLite로, 실전 배포 단계에서는 PostgreSQL이나 MySQL을 사용하게 될 텐데요.

오늘은 그 첫걸음으로 SQLAlchemy ORM을 FastAPI에 통합하는 방법을 알아보겠습니다.

ORM이 뭐고, 왜 쓰는지부터 실제 코드 예제까지 차근차근 풀어볼게요.

초보자도 이해할 수 있도록! 그럼 바로 시작해볼까요? 😄

 

1. 관계형 데이터베이스와 ORM 기초 📘

1.1 관계형 데이터베이스란 무엇인가요?

웹 애플리케이션에서 가장 기본적이면서도 중요한 요소 중 하나는 데이터 저장입니다.

사용자의 정보, 게시글, 댓글 등은 휘발성 메모리보다 영구 저장이 가능한 데이터베이스에 보관해야 하죠.

그리고 그중에서도 가장 널리 쓰이는 건 관계형 데이터베이스(Relational Database)입니다.

관계형 데이터베이스는 데이터를 표(table)의 형태로 저장하고, 그 안에는 열(컬럼)행(레코드)이 존재합니다.

예를 들어 사용자의 정보를 저장할 때는 다음과 같은 테이블이 만들어집니다.

id username email
1 minsu minsu@example.com
2 jiyoon jiyoon@example.com

또한 외래 키(Foreign Key)라는 개념을 이용해서 테이블 간 관계를 설정할 수 있어요.

예를 들어

사용자 테이블과 게시글 테이블이 있다면, 게시글 테이블은 작성자의 ID를 외래 키로 저장함으로써 '누가 쓴 글인지'를 연결할 수 있는 겁니다.

1.2 ORM(Object Relational Mapping)이란?

그런데 여러분, SQL 쿼리 직접 작성해보셨나요?

INSERT INTO, SELECT, UPDATE 같은 명령어들 말이죠. 배우는 건 어렵지 않지만, 반복적으로 작성하다 보면 지치기 마련이에요. 😓

특히 Python처럼 객체 지향 언어에서는 SQL보다 객체를 다루는 게 더 자연스럽죠.

그래서 등장한 게 바로 ORM (Object-Relational Mapping)입니다.

ORM은 객체와 데이터베이스를 자동으로 매핑해주는 기술로, 클래스를 정의하고 객체를 조작하면 자동으로 SQL이 실행돼요.

즉, SQL을 거의 몰라도 DB를 다룰 수 있는 거죠.

  • Python 코드로 SQL 없이 DB 조작 가능
  • 모델 클래스 정의 → 자동으로 테이블 생성
  • 쿼리 결과도 객체로 반환되어 사용이 간편함

ORM을 사용하면 생산성은 높이고, 오류는 줄이고 코드의 일관성까지 챙길 수 있어요.

물론 SQL을 완전히 모르고 개발하는 건 위험하지만, 기본 SQL을 익힌 후 ORM을 활용하면 정말 편리합니다. 😎

자, 이제 ORM의 세계를 본격적으로 들어가 볼 시간이에요.

다음 섹션에서는 FastAPI와 함께 사용할 ORM 라이브러리인 SQLAlchemy를 소개해볼게요!

 

 

2. SQLAlchemy ORM의 기본 구조 이해하기 🧩

2.1 SQLAlchemy란?

SQLAlchemy는 Python 생태계에서 가장 널리 쓰이는 ORM 도구 중 하나입니다.

단순히 ORM 기능뿐 아니라, SQL 생성기, DB 연결기, 트랜잭션 관리자까지 포괄적으로 포함된 종합 프레임워크죠.

SQLAlchemy는 크게 두 가지 레벨로 나뉘는데요.

하나는 Core (SQL 표현 언어), 다른 하나는 ORM (객체 관계 매핑)입니다.

저희는 이 중 ORM을 활용하여 FastAPI와 연결할 거예요.

2.2 SQLAlchemy ORM의 구조

ORM 방식으로 SQLAlchemy를 사용할 때는 다음과 같은 구성요소들을 기억해 두셔야 해요.

각각의 역할이 명확하게 나뉘어 있어서 한 번 구조를 이해해두면 이후 확장도 쉬워집니다.

구성 요소 설명
Engine 데이터베이스와의 연결을 생성하는 객체 (URL 기반으로 DB 접근)
Session ORM이 실제로 DB와 통신할 때 사용하는 작업 단위 (add, commit, query 등 처리)
Base 모든 ORM 모델이 상속받을 추상 클래스 (테이블 메타데이터 포함)
Model 데이터베이스 테이블과 매핑되는 Python 클래스 (컬럼과 필드 정의)

위 구성요소들을 바탕으로 SQLAlchemy로 앱을 구축할 수 있어요.

FastAPI와 통합할 때는 이 구조를 기본으로 시작하게 됩니다.

2.3 SQLAlchemy ORM의 동작 흐름

  1. Engine 생성: create_engine()로 데이터베이스 연결 설정
  2. Session 생성: sessionmaker를 이용해 세션 팩토리 구성
  3. Base 선언: declarative_base()로 모델의 부모 클래스 생성
  4. 모델 정의: 클래스를 통해 테이블 구조와 컬럼 정의
  5. CRUD 작업: 세션을 통해 데이터 생성/조회/수정/삭제 수행

이 흐름을 기억해두면, 이후 FastAPI에서 API 요청 → ORM 처리 → DB 반영 흐름을 구현할 때 큰 도움이 됩니다.

 

그럼 이제 실제로 FastAPI 프로젝트에 SQLAlchemy를 어떻게 통합하는지 본격적으로 코드를 통해 알아볼까요?

다음 섹션에서는 실습 위주로 하나씩 따라가며 설명드릴게요.

 

 

3. FastAPI 프로젝트에 SQLAlchemy 통합 🔗

3.1 SQLite로 로컬 개발 환경 구성하기

FastAPI와 SQLAlchemy를 연동할 때, 처음부터 복잡한 PostgreSQL 같은 DB를 연결하면 진입장벽이 높아질 수 있어요.

그래서 우리는 초보자에게 아주 친숙한 SQLite를 먼저 사용합니다. 설치할 필요도 없고, 파일 하나만 있으면 실행되니까요!

우선 SQLAlchemy 패키지를 설치해요. 터미널에서 아래 명령어를 실행합니다:

pip install sqlalchemy

그 다음, FastAPI 프로젝트 루트 디렉토리에 database.py라는 파일을 만들고

아래처럼 작성해 주세요:

# database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base

SQLALCHEMY_DATABASE_URL = "sqlite:///./app.db"  # 현재 디렉토리에 app.db 생성
engine = create_engine(
    SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)

SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
Base = declarative_base()
  • create_engine: SQLite 파일로 연결되는 엔진 객체 생성
  • sessionmaker: 세션 팩토리. 세션 객체를 생성하여 DB 작업에 사용
  • declarative_base: 모든 모델이 상속받는 베이스 클래스 생성

3.2 DB 세션을 위한 의존성 주입 함수 만들기

FastAPI의 핵심 기능 중 하나인 의존성 주입을 활용해서 각 API 요청마다 자동으로 DB 세션을 열고 닫는 구조를 만들 수 있어요.

아래 코드를 database.py 파일 하단에 추가해보세요:

from sqlalchemy.orm import Session
from fastapi import Depends

def get_db():
    db: Session = SessionLocal()
    try:
        yield db
    finally:
        db.close()

이제 FastAPI 라우터 함수에서는 다음처럼 db: Session = Depends(get_db) 형태로 세션을 자동 주입받을 수 있습니다.

아주 편리하죠? 😄

3.3 구조 정리 – 폴더와 파일 구성은 이렇게

SQLAlchemy를 FastAPI 프로젝트에 통합할 때는 구조화가 중요합니다.

아래처럼 파일을 구성하면 확장성과 유지보수에 훨씬 유리해요.

📁 app/
├── main.py
├── database.py        # DB 연결 설정 및 세션 함수
├── models.py          # ORM 모델 클래스 정의
├── schemas.py         # Pydantic 스키마
└── crud.py            # DB CRUD 함수

이제 다음 단계에서는 실제로 모델을 정의하고 테이블을 생성하는 작업을 해볼 거예요.

드디어 DB와 코드가 연결되는 진짜 실습에 돌입합니다!

 

 

4. ORM 모델 정의와 테이블 생성 🏗️

4.1 모델 클래스 정의하기

ORM에서 가장 중요한 부분 중 하나는 모델 클래스 정의입니다.

여기서 "모델"은 데이터베이스의 테이블과 매핑되는 Python 클래스를 의미해요.

예를 들어 Todo 리스트를 저장하고 싶다면 Todo 모델을 만들어야겠죠?

이제 models.py 파일을 새로 만들어 아래와 같이 작성해봅시다:

# models.py
from sqlalchemy import Column, Integer, String, Boolean
from .database import Base

class Todo(Base):
    __tablename__ = "todos"
    
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String, index=True)
    description = Column(String, nullable=True)
    done = Column(Boolean, default=False)
  • __tablename__: 이 모델이 매핑될 실제 DB 테이블 이름
  • id: 기본 키. 자동 증가 설정
  • title, description: 문자열 컬럼, nullable=True로 NULL 허용
  • done: 완료 여부를 나타내는 불리언(Boolean) 컬럼

4.2 테이블 생성 – 코드로 자동화하기

모델 클래스를 정의했으면, 다음은 실제 SQLite DB 파일에 테이블을 생성해야 합니다.

별도로 SQL을 작성하지 않고도 간단히 처리할 수 있어요.

FastAPI 앱의 진입점인 main.py에 아래 코드를 추가해 주세요:

# main.py
from fastapi import FastAPI
from . import models
from .database import engine

models.Base.metadata.create_all(bind=engine)

app = FastAPI()

create_all() 함수는 Base를 상속받은 모든 모델 클래스의 테이블을 데이터베이스에 자동으로 생성해줍니다.

이미 존재하는 테이블은 건너뛰기 때문에 안심하고 실행해도 돼요.

이제 서버를 실행하면, app.db 파일 안에 todos 테이블이 생기고, 우리가 정의한 컬럼들이 포함돼 있을 거예요. SQLite 브라우저를 통해 직접 확인해보는 것도 재미있겠죠? 😉

4.3 한눈에 보는 코드 구성 요약

파일 내용
database.py DB 연결 엔진, 세션 팩토리, Base 클래스 정의
models.py Todo 모델 클래스 정의
main.py FastAPI 앱 시작 시 테이블 자동 생성

이제 데이터베이스에 연결하고 테이블까지 만들었으니, 본격적으로 데이터를 넣고 꺼내는 CRUD 엔드포인트를 구현해 볼 차례예요.

 

다음 단계에서는 FastAPI 라우터와 SQLAlchemy 세션을 연결해 실제 데이터를 다뤄볼 거예요.

 

 

5. DB 세션 관리 및 CRUD 구현 🛠️

5.1 DB 세션 주입하기 (Dependency Injection)

FastAPI의 가장 멋진 기능 중 하나는 의존성 주입입니다.

각 API 요청에서 DB 세션을 쉽게 주입받을 수 있고, 요청이 끝나면 자동으로 세션을 닫아줘요. 🤩

앞서 만든 get_db() 함수를 기억하시죠? 이를 FastAPI 라우트 함수에 아래처럼 적용하면 됩니다:

from fastapi import Depends
from sqlalchemy.orm import Session
from .database import get_db

이제 API 핸들러에서 db: Session = Depends(get_db)를 인자로 추가하면 DB 연결이 자동으로 처리됩니다.

5.2 CRUD 기능 구현하기

이제 진짜 핵심인 CRUD 구현을 해볼게요!

예시로 Todo 항목을 데이터베이스에서 생성하고 불러오는 코드부터 시작합니다.

먼저 Pydantic 스키마부터 설정할게요:

# schemas.py
from pydantic import BaseModel

class TodoItemCreate(BaseModel):
    title: str
    description: str | None = None

class TodoItem(BaseModel):
    id: int
    title: str
    description: str | None = None
    done: bool

    class Config:
        orm_mode = True

이제 main.py 또는 router 파일에 API 핸들러를 작성해봅시다.

(1) Todo 생성

@app.post("/todos", response_model=schemas.TodoItem)
def create_todo(item: schemas.TodoItemCreate, db: Session = Depends(get_db)):
    db_todo = models.Todo(title=item.title, description=item.description)
    db.add(db_todo)
    db.commit()
    db.refresh(db_todo)
    return db_todo

(2) Todo 단일 조회

@app.get("/todos/{id}", response_model=schemas.TodoItem)
def read_todo(id: int, db: Session = Depends(get_db)):
    todo = db.query(models.Todo).filter(models.Todo.id == id).first()
    if not todo:
        raise HTTPException(status_code=404, detail="Todo not found")
    return todo

(3) Todo 전체 조회

@app.get("/todos", response_model=list[schemas.TodoItem])
def read_all_todos(db: Session = Depends(get_db)):
    return db.query(models.Todo).all()

(4) Todo 상태 업데이트

@app.put("/todos/{id}", response_model=schemas.TodoItem)
def update_done_status(id: int, done: bool, db: Session = Depends(get_db)):
    todo = db.query(models.Todo).filter(models.Todo.id == id).first()
    if not todo:
        raise HTTPException(status_code=404, detail="Todo not found")
    todo.done = done
    db.commit()
    db.refresh(todo)
    return todo

(5) Todo 삭제

@app.delete("/todos/{id}", status_code=204)
def delete_todo(id: int, db: Session = Depends(get_db)):
    todo = db.query(models.Todo).filter(models.Todo.id == id).first()
    if not todo:
        raise HTTPException(status_code=404, detail="Todo not found")
    db.delete(todo)
    db.commit()

5.3 정리: FastAPI + SQLAlchemy 조합의 강력함

  • DB 연결과 세션 관리를 코드 몇 줄로 자동화
  • ORM 모델과 Pydantic 스키마를 분리해 가독성과 유지보수 향상
  • API와 DB가 자연스럽게 연결되어 생산성 UP

이제 API를 실행해보면 SQLite 파일에 데이터가 저장되고, 브라우저나 API 클라이언트에서 요청을 보내도 데이터가 유지되는 걸 확인할 수 있어요.

이건 정말 개발자에게 기쁨이죠! 🎉

 

 

6. 실습 과제: SQLite를 이용한 Todo API 완성 🧪

6.1 실습 목표 정리

이번 섹션에서는 지금까지 배운 내용을 바탕으로 하나의 완성된 Todo API 프로젝트를 직접 만들어보는 게 목표예요. 🚀

FastAPI, SQLAlchemy, SQLite를 연결하여 CRUD 엔드포인트를 직접 구현하고 테스트하는 과정을 통해 백엔드 개발의 핵심을 체감할 수 있습니다.

6.2 실습 내용 정리 – 구현할 기능 목록 ✅

  • POST /todos → Todo 항목 생성
  • GET /todos → 전체 Todo 목록 조회
  • GET /todos/{id} → 특정 Todo 항목 조회
  • PUT /todos/{id}?done=true → 완료 상태 업데이트
  • DELETE /todos/{id} → Todo 항목 삭제

6.3 테스트 방법 및 점검 포인트 🔍

API는 Swagger UI를 통해 쉽게 테스트할 수 있어요.

http://127.0.0.1:8000/docs 주소를 브라우저에 입력하면, 자동 생성된 문서를 통해 각 요청을 테스트해볼 수 있습니다.

 

그리고 아래 항목들을 직접 확인해보세요:

  • POST 요청 후 실제 app.db 파일에 데이터가 저장되는가?
  • 서버를 재시작해도 데이터가 보존되는가?
  • 상태코드 (200, 201, 204, 404 등) 가 올바르게 반환되는가?

6.4 실습 마무리 및 다음 단계 안내 🧭

이번 실습을 통해 FastAPI 애플리케이션에 SQLAlchemy를 통합하고, 데이터를 영구 저장하는 진짜 백엔드 앱을 만들 수 있게 되었습니다. 🎉

 

다음 단계에서는 이 API를 바탕으로 프론트엔드와 연결하거나, 배포 환경을 준비하거나,

다른 DB (예: PostgreSQL)로 전환하는 연습을 해보면 좋겠죠? 😉

 

 

마무리🚀

지금까지 우리는 FastAPI와 SQLAlchemy를 활용해 데이터베이스와 연동되는 Todo API를 구축해봤습니다.

단순한 in-memory 데이터 저장 방식에서 벗어나, 이제는 SQLite 파일을 통해 데이터를 영구 저장하고 관리할 수 있는 수준으로 발전했어요. 🧱

ORM을 도입하면서 코드의 구조가 훨씬 깔끔해지고, 유지보수도 쉬워졌다는 걸 직접 체험해보셨을 겁니다.

특히 FastAPI의 의존성 주입과 SQLAlchemy의 강력한 매핑 기능이 얼마나 잘 어우러지는지도 느껴지셨을 거예요.

 

이제 여러분은 다음 단계로 나아갈 준비가 되었습니다.

PostgreSQL 같은 다른 관계형 DB로 확장하거나, Alembic을 통한 마이그레이션 관리, 혹은 Docker 환경에서의 운영 환경 구축 등 다양한 실전 영역에 도전해보세요!

 

🎯 오늘 다룬 기술을 응용하면 여러분만의 Todo 웹앱, 게시판, 메모장 등 다양한 백엔드 서비스로 발전시킬 수 있어요.  다음 편에서는 FastAPI와 프론트엔드(예: Streamlit)를 연동하는 방법도 소개할 예정입니다!

 

 

반응형

+ Recent posts