콘텐츠로 이동

04. Database 연동: API 서버와 DB 연결하기

"API 서버는 기억상실증에 걸린 것과 같습니다. 서버가 꺼지면 모든 데이터가 사라집니다. 앱의 데이터를 영구적으로 기억하기 위해 우리는 데이터베이스(DB)를 연결해야 합니다."

1. The Barrier: 언어의 장벽 (Python vs SQL)

우리가 만든 FastAPI 서버는 파이썬(Python)으로 작성되어 있습니다. 하지만 데이터베이스는 파이썬을 전혀 모릅니다. DB의 모국어는 SQL이기 때문입니다.

  • 개발자의 고통: 파이썬 코드를 짜다가 갑자기 데이터베이스와 통신하려고 SQL(SELECT * FROM...)을 섞어 쓰면 문법이 헷갈리고 오타가 나기 쉽습니다.
  • SQL의 생김새: 파이썬의 깔끔한 객체 지향 문법과는 전혀 다르게 생겼습니다.
-- SQL: 20살 넘는 사람 다 내놔!
SELECT * FROM users WHERE age > 20;

2. The Solution: 통역사 고용 (SQLModel)

그래서 천재적인 개발자들이 ORM(Object Relational Mapping)이라는 기술을 만들었습니다. 이것은 "파이썬 코드로 명령하면, 알아서 DB가 알아듣는 SQL로 번역해 주는 똑똑한 통역사"입니다.

우리는 최신 FastAPI 생태계에서 가장 각광받는 통역사인 SQLModel을 사용할 것입니다. (FastAPI 창시자가 직접 만들었습니다!)

이 통역사를 쓰면 대화가 이렇게 바뀝니다.

  • Python: select(Student).where(Student.age > 20)
  • ORM (SQLModel): (번역 중...) SELECT * FROM student WHERE age > 20;
  • Database: (데이터 주섬주섬) "여기 있습니다."

🔄 전체 흐름: Frontend → API → Database

아래 시퀀스 다이어그램은 사용자가 브라우저에서 요청을 보냈을 때, 데이터가 어떻게 흘러가는지를 보여줍니다.

sequenceDiagram
    participant Browser as 🖥️ Frontend (Browser)
    participant API as ⚙️ FastAPI (SQLModel)
    participant DB as 🗄️ Database

    Note over Browser, DB: 📖 Read (조회)
    Browser->>API: GET /students
    API->>DB: SELECT * FROM student
    DB-->>API: [학생 데이터 목록]
    API-->>Browser: JSON 응답

    Note over Browser, DB: ✏️ Create (생성)
    Browser->>API: POST /students (이름, 이메일)
    API->>DB: INSERT INTO student ...
    DB-->>API: OK (생성 완료)
    API-->>Browser: 201 Created

    Note over Browser, DB: 🔄 Update (수정)
    Browser->>API: PUT /students/1 (새 이메일)
    API->>DB: UPDATE student SET email=... WHERE id=1
    DB-->>API: OK (수정 완료)
    API-->>Browser: 200 OK

    Note over Browser, DB: 🗑️ Delete (삭제)
    Browser->>API: DELETE /students/1
    API->>DB: DELETE FROM student WHERE id=1
    DB-->>API: OK (삭제 완료)
    API-->>Browser: 204 No Content

💡 핵심 포인트

개발자는 Python(FastAPI) 코드만 작성하면 됩니다. ORM(SQLModel)이 중간에서 자동으로 SQL을 생성하여 DB와 대화합니다.

3. Setup: 파이썬으로 엑셀 표 만들기 (Model 정의)

앞 장에서 배운 Class Diagram이 여기서 빛을 발합니다. 우리가 설계한 student 테이블을 파이썬 클래스로 옮겨보겠습니다. 이때 ORM이 코드를 이해할 수 있도록 SQLModel의 도구들을 함께 사용합니다.

  • SQLModel (신분증): "이 클래스는 일반 파이썬 객체가 아니라, DB 테이블이야!"라고 표시해주는 역할입니다. (table=True 옵션이 핵심입니다.)
  • Field (단어장): 이 칸이 기본키(PK)인지 등의 추가 정보를 통역사에게 알려줍니다.
from sqlmodel import SQLModel, Field

# 1. 파이썬 클래스 정의 (Class Diagram 구현)
class Student(SQLModel, table=True): # table=True를 주면 DB 테이블이 됩니다.
    # 학번 (PK): 안 넣으면 알아서 생성되도록(default=None) 설정
    id: int | None = Field(default=None, primary_key=True)
    name: str # 이름
    email: str # 이메일

결과

이 코드를 실행하면 DB 안에 id, name, email 칸이 있는 student 테이블이 연결됩니다. 참고: 이 설계도 코드는 보통 models.py 파일에 작성합니다.


4. 쿼리의 핵심 기술: 데이터를 지휘하기 (Querying)

"데이터베이스에 100만 명의 학생이 있어도 걱정 마세요. 우리에겐 원하는 학생만 쏙쏙 뽑아내는 쿼리(Query) 기술이 있습니다."

과거에는 db.query()를 많이 썼지만, 최신 SQLModel에서는 세션(Session)select() 문을 조합하여 데이터와 소통합니다.

Step 1. CRUD: 데이터 4대 조작법

데이터베이스가 하는 일은 결국 딱 4가지입니다. 이를 CRUD라고 부르며, 수강신청 시스템의 기본입니다.

구분 영어 (CRUD) 의미 ORM 코드 (SQLModel) 비유 (수강신청)
C Create 생성 session.add(student)session.commit() 신입생 입학
R Read 조회 session.exec(select(Student)).all() 학생 명부 확인
U Update 수정 student.email = "new@..."session.add(student) & commit() 이메일 변경
D Delete 삭제 session.delete(student)session.commit() 자퇴 처리

세션(Session)이란?

DB와 작업을 수행하기 위해 연결된 임시 작업 공간을 뜻합니다. 변경 사항은 반드시 session.commit()을 호출해야 DB에 영구적으로 '저장(도장 쾅!)'됩니다.

Step 2. R(조회)의 심화 기술 1: 까다로운 조건 (Where)

데이터를 찾을 때는 where()를 사용하여 조건을 겁니다.

① 정확히 찾기 (Exact Match)

"이름이 '페이커'인 학생 찾아줘."

from sqlmodel import select

# SQL: SELECT * FROM student WHERE name = '페이커';
statement = select(Student).where(Student.name == "페이커")
faker = session.exec(statement).first()

"성이 '김'씨인 학생 다 찾아줘."

# SQL: SELECT * FROM student WHERE name LIKE '김%';
statement = select(Student).where(Student.name.like("김%"))
kim_students = session.exec(statement).all()

③ 숫자 세기 (Count)

개수를 셀 때 파이썬 리스트의 길이를 구하는 len() 도구를 결합할 수도 있습니다. (더 최적화된 방법도 있지만, 지금은 직관적인 방법을 소개합니다.)

# 모든 학생을 가져와 길이 구하기
students = session.exec(select(Student)).all()
total_count = len(students)

Step 3. 실전 기술: 정렬과 페이징 (Sort & Limit)

학생이 100만 명일 때, 한 번에 다 가져오면 서버가 뻗습니다. 스마트하게 끊어서 가져와야 합니다.

① 정렬 (Order By): 줄 세우기

  • 최신순 (학번 역순): order_by(Student.id.desc())
  • 가나다순: order_by(Student.name.asc())

② 페이징 (Limit & Offset): 끊어 읽기

  • Limit: "10명만 보여줘"
  • Offset: "앞에 0명은 건너뛰고"
# 1페이지 (가입 순서대로 10명 제한)
statement = select(Student).order_by(Student.id.asc()).limit(10).offset(0)
students = session.exec(statement).all()

📝 요약: 쿼리 마스터 공식

  1. Select & Where (조건): select()where()로 원하는 데이터만 고르고,
  2. Order By & Limit (정리): 보기 좋게 줄 세워서 끊고,
  3. Exec (실행): session.exec()로 실행하여 결과를 받는다.

이 단계면 수강신청 시스템의 대부분의 데이터를 제어할 수 있습니다!


5. Architecture: 표준 FastAPI 프로젝트 구조

실제 실무나 규모 있는 프로젝트에서는 코드를 한 파일에 몰아넣지 않고, 역할별로 파일을 나눕니다. 앞서 배운 개념들을 실제 프로젝트에서는 어떻게 배치하는지 알아보겠습니다.

📂 표준 FastAPI 프로젝트 구조 및 역할

파일명 역할 (분리된 이유) 주요 내용
database.py 통로 설정 (접속 정보) DB URL, 엔진 생성, 세션(Session) 생성기 (get_session)
models.py 데이터 설계도 (표) SQLModel을 사용한 테이블 정의 (Student, Course 등)
crud.py 순수 DB 조작 (요리사) create_student, get_student_by_id 등 실제 DB 쿼리 함수들
main.py 입구 및 서빙 (웨이터) FastAPI 앱 객체, API 경로(Route) 정의, 유효성 검사

🛠️ 파일 간 호출 흐름 (Dependency Flow)

이 구조에서 데이터는 보통 아래와 같은 방향으로 흐릅니다.

  1. main.py가 사용자의 요청(Request)을 받습니다.
  2. main.pydatabase.py로부터 DB 세션(열쇠)을 빌려옵니다.
  3. main.py는 빌려온 세션과 사용자 데이터를 crud.py의 함수에 전달합니다.
  4. crud.pymodels.py에 정의된 설계도를 참고하여 DB에 데이터를 넣거나 가져옵니다.
  5. 최종 결과를 main.py가 사용자에게 응답(Response)합니다.

💡 작성 시 주의할 점 (순환 참조 방지)

가장 흔히 하는 실수가 파일끼리 서로를 참조(import)해서 에러가 나는 것입니다. 이 구조를 지키면 안전합니다.

  • models.py는 다른 파일을 import하지 않습니다. (독립적)
  • database.pymodels.py를 몰라도 됩니다.
  • crud.pymodels.pyimport해서 사용합니다.
  • main.pydatabase.py, models.py, crud.py를 모두 import해서 조율합니다.