AI 엔지니어를 위한 FastAPI - 3부: 데이터베이스 연결
Source: Dev.to
이전 글에서는 FastAPI를 사용해 첫 번째 CRUD API를 만드는 과정을 살펴보았습니다. API는 정상적으로 동작했지만, 한 가지 큰 문제가 있었습니다.
우리는 데이터를 메모리 안에 존재하는 Python 리스트에 저장하고 있었습니다.
인스타그램, LinkedIn, 혹은 ChatGPT와 같은 애플리케이션이 서버를 재시작한 뒤에도 정보를 기억하는 이유가 궁금하다면, 답은 간단합니다: 데이터베이스.
이번 글에서는 SQLite와 SQLAlchemy를 이용해 FastAPI 애플리케이션을 데이터베이스에 연결함으로써 메모리 저장 문제를 해결합니다.
이전 글을 아직 읽지 않으셨다면, 먼저 확인해 보세요:
이 글을 다 읽고 나면 다음을 이해하게 됩니다:
- 메모리 저장이 왜 문제인지
- SQLite가 무엇인지
- SQLAlchemy가 무엇인지
- ORM이 어떻게 동작하는지
- Python 클래스로 데이터베이스 테이블을 만드는 방법
- 실제 데이터베이스를 이용해 CRUD 작업을 수행하는 방법
In-Memory Storage의 문제점
이전에는 애플리케이션이 학생 정보를 Python 리스트에 저장했습니다.
students = [
{
"id": 1,
"name": "Ananya",
"department": "CSE",
"cgpa": 8.9
}
]
이는 CRUD 연산을 배우기에 충분했습니다.
하지만 서버가 재시작되면 어떻게 될까요?
FastAPI 서버 중지
↓
Python 메모리 초기화
↓
모든 학생 데이터 손실
실제 서비스에서는 받아들일 수 없는 상황입니다.
애플리케이션이 재시작돼도 데이터를 유지할 수 있는 장소가 필요합니다.
바로 데이터베이스가 그 역할을 합니다.
SQLite란?
SQLite는 가벼운 관계형 데이터베이스입니다.
MySQL이나 PostgreSQL과 달리 별도의 데이터베이스 서버가 필요하지 않으며, 모든 데이터가 하나의 파일에 저장됩니다.
students.db
SQLite의 장점
- 설치가 필요 없음
- 가벼움
- 배우기 쉬움
- 로컬 개발에 최적
- 작은 프로젝트에 적합
이번 글에서는 SQLite를 사용합니다.
SQLAlchemy란?
SQLAlchemy가 등장하기 전에는 개발자들이 직접 순수 SQL 쿼리를 작성했습니다.
예시:
SELECT * FROM students;
SQL은 강력하지만, 코드 곳곳에 쿼리를 흩뿌리면 유지보수가 어려워집니다.
SQLAlchemy는 ORM을 통해 이 문제를 해결합니다.
ORM이란?
ORM은 Object Relational Mapper의 약자입니다.
데이터베이스 테이블을 Python 클래스와 매핑해, 객체 지향 방식으로 데이터베이스를 다룰 수 있게 해줍니다. 일종의 번역가 역할을 합니다.
Database ↔ Python
Table ↔ Class
Row ↔ Object
Column ↔ Attribute
예시:
데이터베이스 테이블
students
id name department cgpa
1 Ananya CSE 8.9
Python 클래스
class Student(Base):
...
SQL을 직접 작성하는 대신 Python 객체를 다루게 되며, SQLAlchemy가 내부적으로 SQL을 생성합니다.
프로젝트 구조
다음과 같은 디렉터리 구조를 만든다:
project/
│
├── database.py
├── models.py
├── schemas.py
├── main.py
└── students.db
각 파일은 다음과 같은 역할을 담당합니다.
- database.py: 데이터베이스 연결, 세션 생성, Base 클래스 정의
- models.py: 데이터베이스 테이블 정의
- schemas.py: 요청 검증 및 응답 구조 정의
- main.py: API 라우트와 비즈니스 로직
- students.db: 실제 SQLite 파일
의존성 설치
pip install sqlalchemy
FastAPI를 아직 설치하지 않았다면:
pip install fastapi uvicorn
Step 1: database.py 만들기
database.py 파일을 생성하고 다음 코드를 넣는다.
from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "sqlite:///./students.db"
engine = create_engine(
DATABASE_URL,
connect_args={"check_same_thread": False} # 스레드 간에 동일한 연결을 사용할 수 있게 함
)
SessionLocal = sessionmaker(
autocommit=False,
autoflush=False,
bind=engine
)
Base = declarative_base()
보통 SQLAlchemy는 트랜잭션 모드로 동작합니다.
변경 사항을 세션에 쌓아두었다가 commit()을 호출하면 영구 저장됩니다.
autocommit이 활성화되면 각 문장이 즉시 커밋됩니다(예: SQLite 기본 동작).
autoflush=True(기본값)일 경우, 쿼리를 실행하기 전에 보류 중인 변경 사항을 자동으로 데이터베이스에 플러시합니다.
플러시란: 현재 트랜잭션 안에서 메모리상의 변경을 데이터베이스와 동기화하는 작업이며, 아직 커밋되지 않아 롤백이 가능합니다.
create_engine() 이해하기
engine = create_engine(...)
SQLAlchemy가 데이터베이스와 통신하기 위해 엔진 객체가 필요합니다. 엔진은 FastAPI와 SQLite 사이의 다리 역할을 합니다. 삽입, 조회, 수정, 삭제 등 모든 데이터베이스 작업이 엔진을 통해 이루어집니다.
SessionLocal 이해하기
SessionLocal = sessionmaker(...)
세션은 데이터베이스와의 대화를 의미합니다. 은행에 방문해 대화를 시작하고, 여러 거래를 수행한 뒤 대화를 종료하는 것과 비슷합니다. 모든 데이터베이스 작업은 세션을 통해 이루어집니다.
Base 이해하기
Base = declarative_base()
우리가 정의하는 모든 모델은 Base를 상속받습니다. SQLAlchemy는 Base를 통해 모델들을 추적하고 테이블을 자동으로 생성합니다.
데이터베이스 세션 만들기
앞의 코드 아래에 다음 함수를 추가한다.
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
왜 get_db()가 필요한가?
이 함수를 사용하지 않으면 각 라우트마다 세션을 직접 만들고 닫아야 합니다.
예시:
@app.get("/students")
def get_students():
db = SessionLocal()
# 데이터베이스 작업
db.close()
코드가 중복됩니다. FastAPI는 Depends(get_db)를 통해 세션을 자동으로 생성·제공·종료해 줍니다. 이를 Dependency Injection이라고 합니다.
Step 2: models.py 만들기
models.py 파일을 생성하고 다음 코드를 넣는다.
from sqlalchemy import Column, Integer, String, Float
from database import Base
class Student(Base):
__tablename__ = "students"
id = Column(Integer, primary_key=True, index=True)
name = Column(String)
department = Column(String)
cgpa = Column(Float)
모델 이해하기
__tablename__ = "students"
위 코드는 students라는 이름의 테이블을 생성합니다.
id = Column(Integer, primary_key=True)
기본 키를 정의합니다. 각 학생은 고유한 ID를 가져야 합니다.
name = Column(String)
텍스트 컬럼을 생성합니다. department도 동일하게 문자열 컬럼입니다.
cgpa = Column(Float)