안녕하세요, IT와 테크 지식을 공부하고 기록하는 루카(Luka)입니다.
오늘 함께 알아볼 주제는 바로 'FastAPI'입니다. 파이썬 웹 프레임워크 생태계는 플라스크(Flask)와 장고(Django)가 양분하고 있었지만, 최근 몇 년 사이 놀라운 속도와 생산성으로 무장한 FastAPI가 등장하며 판도를 바꾸고 있습니다. 특히 고성능 API 서버 개발에 있어서 FastAPI는 독보적인 위치를 차지하고 있죠. 이번 포스팅에서는 FastAPI가 왜 빠른지, 어떻게 쉽고 강력하게 API를 구축할 수 있는지, 그리고 실전에서 유용한 팁까지 모두 살펴보겠습니다. 느린 API 때문에 고민이셨다면, 이 글이 여러분의 개발 여정에 큰 도움이 될 것이라고 확신합니다.
FastAPI, 도대체 무엇이 특별할까요?
FastAPI는 현대 파이썬에서 고성능 웹 API를 구축하기 위한 프레임워크입니다. 그 이름처럼 'Fast(빠름)'에 초점을 맞추고 있으며, 비동기(async/await) 기능을 완벽하게 지원하고 파이썬 타입 힌트를 적극적으로 활용하여 개발 생산성을 극대화합니다.
핵심 특징 요약:
- 압도적인 성능: Starlette 웹 서버(ASGI)와 Pydantic 데이터 유효성 검사 라이브러리를 기반으로 합니다. Node.js나 Go 프레임워크에 버금가는 속도를 자랑합니다.
- 자동 문서화: OpenAPI(Swagger UI, ReDoc) 표준을 완벽히 준수하여 API 문서를 자동으로 생성해줍니다. 개발자는 코드를 작성하는 것만으로 최신 API 문서를 얻을 수 있습니다.
- 생산적인 개발 경험: 파이썬 타입 힌트 덕분에 코드 자동 완성, 오류 검출, 데이터 유효성 검사가 매우 강력해집니다. 별도의 유효성 검사 코드를 작성할 필요가 거의 없습니다.
- 비동기 지원:
async/await문법을 네이티브로 지원하여 I/O 바운드 작업(데이터베이스 쿼리, 외부 API 호출 등)에서 탁월한 성능을 발휘합니다. - 의존성 주입(Dependency Injection): 테스트 용이성과 코드 재사용성을 높여주는 강력한 의존성 관리 시스템을 내장하고 있습니다.
왜 FastAPI를 선택해야 할까요?
기존 파이썬 웹 프레임워크(Flask, Django REST Framework 등)가 여전히 강력하지만, 특정 사용 사례에서는 FastAPI가 훨씬 유리합니다.
- 성능이 최우선이라면: 마이크로서비스 아키텍처에서 고성능 API 게이트웨이나 CPU/I/O 집약적인 작업을 처리해야 할 때 FastAPI는 최고의 선택지입니다.
- 빠른 개발과 유지보수: 타입 힌트와 Pydantic의 조합은 개발 초기 단계의 오류를 줄이고, 나중에 코드를 이해하고 유지보수하는 데 드는 시간을 획기적으로 단축시켜 줍니다.
- 명확한 API 계약: 자동 생성되는 문서는 프론트엔드 개발자나 외부 API 사용자와의 협업을 매우 원활하게 만듭니다. API 스펙이 변경될 때마다 수동으로 문서를 업데이트할 필요가 없습니다.
FastAPI 시작하기: 기본 환경 설정 및 첫 API
FastAPI를 시작하는 것은 매우 간단합니다.
1. 설치
pip install fastapi "uvicorn[standard]"
uvicorn[standard]는 Uvicorn 서버를 포함하며, uvicorn은 ASGI 서버로 FastAPI 애플리케이션을 실행하는 데 사용됩니다.
2. 첫 번째 FastAPI 애플리케이션 작성 (main.py)
from fastapi import FastAPI
from typing import Optional # Python 3.9 이하에서 필요 (3.10부터 Union[str, None] 대신 str | None 사용 가능)
app = FastAPI()
@app.get("/")
async def read_root():
return {"message": "안녕하세요, FastAPI 세계에 오신 것을 환영합니다!"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Optional[str] = None): # Python 3.9 이하
# async def read_item(item_id: int, q: str | None = None): # Python 3.10 이상
if q:
return {"item_id": item_id, "q": q}
return {"item_id": item_id}
3. 애플리케이션 실행
터미널에서 다음 명령어를 실행합니다.
uvicorn main:app --reload
main:main.py파일 (Python 모듈)app:main.py파일 내부에 있는FastAPI()객체--reload: 코드 변경 시 서버를 자동으로 재시작하여 개발 편의성을 높입니다.
이제 웹 브라우저에서 http://127.0.0.1:8000에 접속하면 {"message": "안녕하세요, FastAPI 세계에 오신 것을 환영합니다!"}를 볼 수 있습니다.
http://127.0.0.1:8000/items/5?q=somequery 와 같은 URL로 접속하여 파라미터가 작동하는 것을 확인할 수 있습니다.
자동 생성되는 API 문서 확인
FastAPI는 자동으로 두 가지 형태의 API 문서를 생성합니다.
* Swagger UI: http://127.0.0.1:8000/docs
* ReDoc: http://127.0.0.1:8000/redoc
이 URL에 접속하여 방금 만든 API들의 인터랙티브한 문서를 직접 확인해보세요!
핵심 개념 파고들기
FastAPI를 효과적으로 사용하기 위한 몇 가지 핵심 개념을 더 자세히 살펴보겠습니다.
Path Parameters와 Query Parameters
앞선 예제에서 item_id는 Path Parameter, q는 Query Parameter였습니다.
- Path Parameters: URL 경로의 일부로, 특정 리소스를 식별할 때 사용됩니다.
@app.get("/items/{item_id}")와 같이 중괄호{}로 정의하며, 함수 인자에 동일한 이름으로 타입 힌트와 함께 받습니다. (item_id: int) - Query Parameters: URL에
?뒤에key=value형태로 붙는 선택적 매개변수입니다. 함수 인자에 기본값을None으로 설정하거나Optional[str]등을 사용하여 선택적으로 만들 수 있습니다.
Request Body: Pydantic 모델 활용
POST, PUT 요청과 같이 클라이언트가 서버로 데이터를 보낼 때는 Request Body를 사용합니다. FastAPI는 Pydantic 라이브러리와 연동하여 매우 강력하고 간편한 데이터 유효성 검사 및 직렬화/역직렬화 기능을 제공합니다.
from typing import List, Optional
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None # Python 3.9 이하
# description: str | None = None # Python 3.10 이상
price: float
tax: Optional[float] = None # Python 3.9 이하
# tax: float | None = None # Python 3.10 이상
# 임시 데이터베이스 역할
items_db = []
@app.post("/items/", response_model=Item)
async def create_item(item: Item):
items_db.append(item.model_dump()) # Pydantic v2 이후 .dict() -> .model_dump()
return item
@app.get("/items/", response_model=List[Item])
async def read_all_items():
return items_db
@app.get("/items/{item_id}", response_model=Item)
async def read_item_by_id(item_id: int):
if item_id < len(items_db):
return items_db[item_id]
# 실제 프로덕션 코드에서는 HTTPException을 사용하는 것이 좋습니다.
# raise HTTPException(status_code=404, detail="Item not found")
return {"message": "Item not found"}
위 코드에서 Item(BaseModel) 클래스는 Pydantic 모델입니다. FastAPI는 요청으로 들어온 JSON 데이터를 자동으로 Item 모델에 맞춰 유효성을 검사하고 파싱해줍니다. 만약 데이터 형식이 맞지 않다면, 친절한 오류 메시지와 함께 422 Unprocessable Entity 응답을 자동으로 반환합니다.
응답 모델 (Response Model)
@app.get("/items/", response_model=List[Item])과 같이 response_model 인자를 사용하면, API 응답이 어떤 형식으로 나갈지 미리 정의할 수 있습니다. 이는 문서화에 큰 도움이 될 뿐만 아니라, 실제로 반환되는 데이터의 필드를 제한하거나 변환하는 데도 사용될 수 있습니다. 민감한 정보가 응답에 포함되지 않도록 제어할 때 유용합니다.
심화 팁 및 모범 사례
FastAPI를 실전에 적용할 때 도움이 될 만한 몇 가지 팁입니다.
1. 비동기 작업 활용 (async/await)
FastAPI는 비동기 프레임워크인 Starlette 위에 구축되었기 때문에 async def로 정의된 함수는 비동기적으로 실행됩니다. 이는 특히 데이터베이스 쿼리, 외부 API 호출, 파일 I/O 등 I/O 바운드 작업이 많은 애플리케이션에서 빛을 발합니다. 작업을 await 키워드를 사용하여 대기시킴으로써, 해당 작업이 완료되는 동안에도 서버가 다른 요청을 처리할 수 있게 됩니다. 이는 동기 방식 웹 서버보다 훨씬 높은 동시성을 제공합니다.
import asyncio
from fastapi import FastAPI
app = FastAPI()
async def fetch_data_from_db():
# 실제 데이터베이스 호출 대신 비동기적으로 2초 대기
await asyncio.sleep(2)
return {"data": "Fetched from DB"}
@app.get("/slow-api/")
async def slow_api_endpoint():
data = await fetch_data_from_db()
return data
asyncio.sleep()처럼 await 가능한 객체를 기다려주면, 해당 시간 동안 CPU는 다른 작업을 할 수 있게 됩니다.
2. 의존성 주입 (Dependency Injection)
FastAPI의 의존성 주입 시스템은 코드의 모듈성과 테스트 용이성을 극대화합니다. 데이터베이스 세션, 인증 토큰, 현재 사용자 정보 등을 함수 인자로 쉽게 주입받을 수 있습니다.
from fastapi import Depends, FastAPI, HTTPException, status
from typing import Annotated
app = FastAPI()
# 가상의 데이터베이스 연결
def get_db_connection():
try:
db = "가상의 DB 연결 객체"
yield db
finally:
print("DB 연결 종료")
# db.close() # 실제 DB 연결이라면 여기서 연결을 닫습니다.
@app.get("/items/with-db/")
async def get_items_with_db(db: Annotated[str, Depends(get_db_connection)]):
# db 객체를 사용하여 작업 수행
return {"message": f"DB를 통해 아이템 조회: {db}"}
Depends()를 통해 get_db_connection 함수가 호출되며, 그 결과가 db 인자로 주입됩니다. yield를 사용하면 요청 처리 후 finally 블록의 코드가 실행되어 자원 반납 로직을 구현하기 편리합니다.
3. 오류 처리 (Error Handling)
HTTPException을 사용하여 표준 HTTP 오류 응답을 쉽게 보낼 수 있습니다.
from fastapi import FastAPI, HTTPException, status
app = FastAPI()
@app.get("/users/{user_id}")
async def get_user(user_id: int):
if user_id != 123:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found",
headers={"X-Error": "There goes my custom error header"},
)
return {"user_id": user_id, "name": "루카"}
status.HTTP_404_NOT_FOUND와 같이 status 모듈의 상수를 사용하면 코드를 더 명확하게 만들 수 있습니다.
4. 테스트 (Testing)
FastAPI는 TestClient를 제공하여 애플리케이션을 쉽게 테스트할 수 있도록 합니다. 이는 Starlette의 TestClient를 기반으로 합니다.
from fastapi.testclient import TestClient
from main import app # 위에서 작성한 FastAPI 앱 객체 (main.py 파일을 가정)
client = TestClient(app)
def test_read_main():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "안녕하세요, FastAPI 세계에 오신 것을 환영합니다!"}
def test_read_item():
response = client.get("/items/10")
assert response.status_code == 200
assert response.json() == {"item_id": 10}
def test_create_item():
response = client.post(
"/items/",
json={"name": "테스트 아이템", "price": 9.99} # description, tax는 Optional이라 생략 가능
)
assert response.status_code == 200
assert response.json()["name"] == "테스트 아이템"
간단한 테스트 코드로 API의 동작을 검증할 수 있습니다.
마무리하며
지금까지 FastAPI를 활용하여 초고속 파이썬 웹 API 서버를 개발하는 방법에 대해 알아보았습니다. FastAPI는 강력한 성능, 뛰어난 개발자 경험, 그리고 자동 문서화라는 세 가지 축을 기반으로 현대 API 개발에 필요한 거의 모든 기능을 제공합니다. 특히 파이썬 타입 힌트를 적극적으로 활용하여 코드의 견고성을 높이고 개발 시간을 단축하는 점은 다른 프레임워크와 차별화되는 지점입니다.
물론 FastAPI가 모든 프로젝트에 정답은 아닙니다. 복잡한 웹 애플리케이션 백엔드나 ORM(Object-Relational Mapping)이 필수적인 대규모 프로젝트에는 Django와 같은 풀스택 프레임워크가 더 적합할 수도 있습니다. 하지만 마이크로서비스 아키텍처, 고성능 API 게이트웨이, 그리고 빠른 프로토타이핑이 필요한 경우라면 FastAPI는 여러분의 개발 생산성과 애플리케이션 성능을 한 차원 높여줄 최고의 도구가 될 것입니다.
오늘 포스팅에서 다룬 내용을 바탕으로 여러분만의 멋진 FastAPI 프로젝트를 시작해 보시길 바랍니다. 궁금한 점이 있다면 언제든지 댓글로 남겨주세요! 다음번에는 더 유익한 주제로 찾아오겠습니다.
감사합니다. 루카(Luka) 드림.