FastAPI로 간단한 Hero API 만들기

FastAPI로 간단한 Hero API 만들기 (Simple Hero API with FastAPI)

FastAPI로 간단한 히어로 웹 API를 만들어 보는 것으로 시작할게요. ✨

출처: 공식문서

FastAPI 설치하기

첫 단계는 FastAPI를 설치하는 거예요.

FastAPI는 웹 API를 만드는 프레임워크예요.

프로젝트에 추가해 볼게요:

uv add fastapi "uvicorn[standard]"

SQLModel 코드 - 모델, 엔진

이제 SQLModel 코드를 시작해 볼게요.

가장 간단한 버전, 히어로만 있는 버전(아직 팀은 없어요)부터 시작할게요.

이건 지금까지 앞선 예시들에서 봤던 코드와 거의 같아요:

# Code above omitted 👆

from sqlmodel import Field, Session, SQLModel, create_engine, select

# Code here omitted 👈

class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: int | None = Field(default=None, index=True)


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, echo=True, connect_args=connect_args)


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

# Code below omitted 👇

여기서 우리가 그동안 써온 코드와 달라진 단 하나는 connect_args 안의 check_same_thread예요.

이것은 SQLAlchemy가 데이터베이스와 통신을 담당하는 저수준 라이브러리에 전달하는 설정이에요.

check_same_thread는 기본적으로 True로 설정되어 있어요. 몇 가지 단순한 경우의 오용을 막기 위해서죠.

하지만 여기서는 같은 세션을 두 개 이상의 요청에서 공유하지 않도록 확실히 하려고 해요. 그리고 그것이 바로 그 설정이 존재하는 이유인 문제들을 막는 가장 안전한 방법이에요.

그리고 이것을 비활성화해야 하는 이유는, FastAPI에서 각 요청이 여러 개의 상호작용하는 스레드에 의해 처리될 수 있기 때문이에요.

📌 참고

지금은 이 정도면 충분해요. 더 자세한 내용은 FastAPI 문서의 async와 await 부분에서 읽을 수 있어요.

핵심은, 같은 세션을 두 개 이상의 요청과 공유하지 않도록 보장하면 코드는 이미 안전하다는 거예요.

FastAPI 앱

다음 단계는 FastAPI 앱을 만드는 거예요.

fastapi에서 FastAPI 클래스를 가져올게요.

그리고 그 FastAPI 클래스의 인스턴스인 app 객체를 만들 거예요:

from fastapi import FastAPI
from sqlmodel import Field, Session, SQLModel, create_engine, select

# Code here omitted 👈

app = FastAPI()

# Code below omitted 👇

시작할 때 데이터베이스와 테이블 만들기

앱이 실행되기 시작하면 create_db_and_tables 함수가 호출되도록 하고 싶어요. 데이터베이스와 테이블을 만들기 위해서죠.

이것은 매 요청마다가 아니라 시작할 때 단 한 번만 호출되어야 하므로, "startup" 이벤트를 처리하는 함수 안에 넣어요:

# Code above omitted 👆

app = FastAPI()


@app.on_event("startup")
def on_startup():
    create_db_and_tables()

# Code below omitted 👇

Hero 생성 Path Operation

📌 참고

Path Operation이 무엇인지(특정 HTTP Operation이 있는 엔드포인트)와 FastAPI에서 어떻게 다루는지 다시 볼 필요가 있다면, FastAPI docs의 First Steps를 확인해 보세요.

새 히어로를 생성하는 path operation 코드를 만들어 볼게요.

사용자가 POST operation으로 /heroes/ 경로에 요청을 보내면 이 함수가 호출될 거예요:

# Code above omitted 👆

app = FastAPI()


@app.on_event("startup")
def on_startup():
    create_db_and_tables()


@app.post("/heroes/")
def create_hero(hero: Hero):
    with Session(engine) as session:
        session.add(hero)
        session.commit()
        session.refresh(hero)
        return hero

# Code below omitted 👇

📌 참고

그 개념들 중 일부를 다시 볼 필요가 있다면 FastAPI 문서를 확인해 보세요:

  • First Steps
  • Path Parameters - Data Validation and Data Conversion
  • Request Body

SQLModel의 장점

우리의 SQLModel 클래스 모델이 SQLAlchemy 모델과 Pydantic 모델을 동시에 겸한다는 점이 여기서 빛을 발해요. ✨

여기서 우리는 같은 클래스 모델을 API가 받을 요청 본문을 정의하는 데 사용해요.

FastAPI는 Pydantic을 기반으로 하므로, 동일한 모델(Pydantic 부분)을 사용해서 JSON 요청에서 Hero 클래스의 실제 인스턴스인 객체로 자동 데이터 검증과 변환을 해줘요.

그리고 이 같은 SQLModel 객체가 Pydantic 모델 인스턴스일 뿐만 아니라 SQLAlchemy 모델 인스턴스이기도 하므로, 세션에서 직접 사용해 데이터베이스에 그 행을 만들 수 있어요.

그래서 우리는 직관적인 표준 Python 타입 어노테이션을 사용할 수 있고, 데이터베이스 모델과 API 데이터 모델을 위한 코드를 많이 복제하지 않아도 돼요. 🎉

💡 팁

나중에 이것을 더 개선하겠지만, 지금도 SQLModel 클래스가 SQLAlchemy 모델과 Pydantic 모델을 동시에 겸하는 것의 힘을 이미 보여 주고 있어요.

Hero 읽기 Path Operation

이제 모든 히어로를 읽는 path operation을 하나 더 추가해 볼게요:

# Code above omitted 👆

app = FastAPI()


@app.on_event("startup")
def on_startup():
    create_db_and_tables()


@app.post("/heroes/")
def create_hero(hero: Hero):
    with Session(engine) as session:
        session.add(hero)
        session.commit()
        session.refresh(hero)
        return hero


@app.get("/heroes/")
def read_heroes():
    with Session(engine) as session:
        heroes = session.exec(select(Hero)).all()
        return heroes

👀 전체 파일 미리보기 (Full file preview)

from fastapi import FastAPI
from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: int | None = Field(default=None, index=True)


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, echo=True, connect_args=connect_args)


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


app = FastAPI()


@app.on_event("startup")
def on_startup():
    create_db_and_tables()


@app.post("/heroes/")
def create_hero(hero: Hero):
    with Session(engine) as session:
        session.add(hero)
        session.commit()
        session.refresh(hero)
        return hero


@app.get("/heroes/")
def read_heroes():
    with Session(engine) as session:
        heroes = session.exec(select(Hero)).all()
        return heroes

이건 꽤 간단해요.

클라이언트가 GET HTTP operation으로 /heroes/ 경로에 요청을 보내면, 이 함수를 실행해서 데이터베이스에서 히어로들을 가져와 반환해요.

요청당 하나의 세션

조작의 그룹마다 SQLModel 세션 하나를 사용하고, 관련 없는 다른 조작이 필요하면 다른 세션을 사용해야 한다는 걸 기억하시나요?

여기서는 그것이 훨씬 더 명확해요.

대부분의 경우 요청 하나당 세션 하나를 사용해야 해요.

일부 고립된 경우에는 안에 새 세션을 만들고 싶을 수 있으니, 요청당 두 개 이상의 세션이 되는 경우도 있어요.

하지만 서로 다른 요청들 사이에 같은 세션을 공유하는 일은 절대 원하지 않아요.

이 간단한 예시에서는 그냥 path operation 함수 안에서 새 세션을 수동으로 만들어요.

나중의 예시들에서는 세션을 얻기 위해 FastAPI Dependency를 사용할 거예요. 그러면 다른 dependency들과 공유할 수 있고, 테스트 중에 교체할 수도 있죠. 🤓

개발 모드에서 FastAPI 서버 실행하기

이제 FastAPI 애플리케이션을 실행할 준비가 됐어요.

이 모든 코드를 main.py라는 파일에 넣어요.

그런 다음 fastapi CLI로 개발 모드에서 실행해요:

uv run fastapi dev main.py

📌 참고

fastapi 명령은 내부적으로 Uvicorn을 사용해요.

fastapi dev를 사용하면 코드를 변경할 때마다 자동으로 다시 로드하는 옵션으로 Uvicorn을 시작해요. 이렇게 하면 더 빨리 개발할 수 있어요. 🤓

프로덕션 모드에서 FastAPI 서버 실행하기

개발 모드는 프로덕션에서 사용하면 안 돼요. 기본적으로 자동 리로드를 포함하는데, 이는 필요한 것보다 훨씬 많은 리소스를 소비하고 오류가 나기 쉬우니까요.

프로덕션에서는 fastapi dev 대신 fastapi run을 사용해요:

uv run fastapi run main.py

API 문서 UI 확인하기

이제 브라우저에서 http://127.0.0.1:8000 URL로 갈 수 있어요. 우리는 루트 경로 /에 대한 path operation을 만들지 않았으므로, 그 URL 단독으로는 "Not Found" 오류만 보여줄 거예요... 그 "Not Found" 오류는 여러분의 FastAPI 애플리케이션이 만들어 낸 거예요.

하지만 /docs 경로, 즉 http://127.0.0.1:8000/docs 에서 자동 생성된 대화형 API 문서로 갈 수 있어요. ✨

이 자동 API 문서 UI가 위에서 우리가 정의한 경로들과 그 operation들을 갖고 있고, path operation이 받을 데이터의 형태를 이미 알고 있다는 걸 볼 수 있을 거예요.

API로 놀아보기

실제로 Try it out 버튼을 클릭해서 Create Hero path operation으로 요청을 보내 히어로들을 만들 수 있어요.

그런 다음 Read Heroes path operation으로 그들을 다시 받아올 수 있어요.

데이터베이스 확인하기

이제 터미널로 돌아가 Ctrl+C를 눌러 그 서버 프로그램을 종료할 수 있어요.

그런 다음 DB Browser for SQLite를 열어 데이터베이스를 확인해서, 데이터를 탐색하고 실제로 히어로들이 저장됐는지 확인할 수 있어요. 🎉

정리

잘했어요! 이제 히어로 데이터베이스와 상호작용하는 FastAPI 웹 API 애플리케이션이 생겼어요. 🎉

개선하고 확장할 수 있는 것들이 몇 가지 있어요. 예를 들어, 우리는 데이터베이스가 각 새 히어로의 ID를 결정하길 원해요. 사용자가 그걸 보내도록 허용하고 싶지 않거든요.

그 모든 개선은 다음 장들에서 하게 될 거예요. 🚀

더 알아보기 (Learn more)