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를 결정하길 원해요. 사용자가 그걸 보내도록 허용하고 싶지 않거든요.
그 모든 개선은 다음 장들에서 하게 될 거예요. 🚀