파이썬(Python) 웹 개발 생태계는 오랫동안 Django의 안정성과 Flask의 유연성이라는 두 거인의 영향력 아래 있었습니다. 그러나 기술의 발전, 특히 비동기(Asynchronous) 프로그래밍에 대한 요구가 급증하면서 새로운 패러다임이 필요하게 되었습니다. 바로 이 지점에서 FastAPI가 혜성처럼 등장했습니다. FastAPI는 단순히 또 하나의 웹 프레임워크가 아닙니다. 이는 파이썬의 최신 기능인 타입 힌트(Type Hints)를 적극적으로 활용하고, 비동기 처리의 성능을 극대화하여 개발 속도와 실행 속도 모두를 혁신적으로 개선한, 현대적인 API 서버 구축을 위한 새로운 표준을 제시합니다.
이 글은 FastAPI의 기본적인 사용법을 나열하는 데 그치지 않습니다. 왜 FastAPI가 현시점에서 최상의 선택지 중 하나인지, 그 핵심을 이루는 비동기 처리와 Pydantic 데이터 검증, 의존성 주입(Dependency Injection)이 어떻게 유기적으로 결합하여 놀라운 개발자 경험(Developer Experience, DX)과 압도적인 성능을 제공하는지 심층적으로 분석합니다. 단순히 '빠르다'는 사실을 넘어, 그 속도가 어디에서 오는지, 그리고 그 속도를 유지하면서 어떻게 안정적이고 확장 가능한 API를 설계하고 구축할 수 있는지에 대한 진실에 초점을 맞출 것입니다. 기본 개념부터 실제 데이터베이스 연동, 인증, 배포 전략까지, FastAPI를 활용한 고성능 API 서버 구축의 전 과정을 함께 탐험해 보겠습니다.
왜 우리는 FastAPI에 주목해야 하는가?
새로운 기술을 도입하기 전, 개발자는 항상 '왜?'라는 질문을 던져야 합니다. 기존의 도구들로도 충분히 해결 가능한 문제라면, 굳이 학습 곡선을 감수하며 새로운 스택으로 전환할 필요가 없기 때문입니다. FastAPI는 이 '왜?'라는 질문에 매우 명확하고 강력한 답변을 제시합니다. 그것은 바로 '성능'과 '개발 생산성'이라는, 현대 웹 개발의 가장 중요한 두 가치를 동시에, 그리고 압도적으로 충족시킨다는 점입니다.
전통적 프레임워크의 한계와 비동기 패러다임의 부상
Django와 Flask 같은 전통적인 파이썬 웹 프레임워크는 WSGI(Web Server Gateway Interface)를 기반으로 하는 동기(Synchronous) 방식으로 동작합니다. 동기 방식은 코드가 위에서 아래로 순차적으로 실행되며, 하나의 작업이 완료될 때까지 다음 작업은 대기해야 합니다. 이는 CPU 연산 중심의 작업에서는 합리적인 방식이지만, 현대 웹 애플리케이션의 대부분을 차지하는 I/O 바운드(I/O-bound) 작업에서는 심각한 성능 저하를 유발합니다.
I/O 바운드 작업이란 데이터베이스 조회, 외부 API 호출, 파일 읽기/쓰기 등 프로그램의 실행이 CPU가 아닌 입출력 장치의 응답을 기다리는 데 대부분의 시간을 소비하는 작업을 의미합니다. 예를 들어, 동기 방식의 서버가 데이터베이스에 쿼리를 보내고 100ms 동안 응답을 기다린다면, 그 100ms 동안 서버 프로세스(또는 스레드)는 아무 일도 하지 못하고 그대로 멈춰있게 됩니다. 이는 곧 시스템 자원의 낭비로 이어집니다.
이 문제를 해결하기 위해 등장한 것이 바로 비동기(Asynchronous) 프로그래밍 패러다임입니다. 비동기는 특정 작업이 완료되기를 기다리지 않고, 대기 시간 동안 다른 작업을 처리하는 방식입니다. 마치 유능한 바리스타가 커피 머신에서 에스프레소를 내리는 동안(대기 시간) 다른 손님의 주문을 받거나 우유 거품을 만드는 것과 같습니다. 파이썬에서는 `asyncio` 라이브러리와 `async`/`await` 문법을 통해 이를 지원하며, FastAPI는 이 비동기 패러다임 위에서 설계되었습니다. ASGI(Asynchronous Server Gateway Interface)를 기반으로 하여, I/O 대기 시간 동안 다른 요청을 효율적으로 처리함으로써 동기 프레임워크 대비 수십 배 높은 동시 처리 성능을 발휘할 수 있습니다.
# 동기 방식의 예 (Flask)
# 하나의 요청이 1초간 멈추면, 다른 요청은 무조건 대기해야 한다.
import time
from flask import Flask
app = Flask(__name__)
@app.route("/")
def sync_task():
time.sleep(1) # 1초 동안 블로킹(Blocking) I/O 발생 (가정)
return {"message": "동기 작업 완료!"}
# 비동기 방식의 예 (FastAPI)
# 하나의 요청이 1초간 대기하는 동안, 이벤트 루프는 다른 요청을 처리한다.
import asyncio
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def async_task():
await asyncio.sleep(1) # 1초 동안 논블로킹(Non-blocking) I/O 발생
return {"message": "비동기 작업 완료!"}
FastAPI를 구성하는 두 개의 기둥: Starlette와 Pydantic
FastAPI의 우수성은 단순히 `asyncio`를 사용했다는 점에서 끝나지 않습니다. 그 핵심에는 이미 검증된 두 개의 강력한 라이브러리, Starlette와 Pydantic이 자리 잡고 있습니다.
- Starlette: FastAPI의 성능과 비동기 기능의 근간을 이루는 초경량 ASGI 프레임워크입니다. WebSocket, GraphQL, CORS 등 현대 웹 애플리케이션에 필요한 필수 기능들을 매우 높은 성능으로 제공합니다. FastAPI는 Starlette의 모든 기능을 그대로 상속받으면서, 그 위에 개발자 친화적인 기능들을 추가한 형태라고 볼 수 있습니다. 즉, 성능의 핵심은 Starlette에서, 편리함의 핵심은 FastAPI의 추가 계층에서 오는 것입니다.
- Pydantic: 파이썬의 타입 힌트를 활용하여 데이터 유효성 검사, 직렬화(Serialization), 그리고 문서화를 자동으로 처리하는 라이브러리입니다. 개발자는 그저 파이썬 클래스와 타입 힌트를 사용하여 데이터 모델을 정의하기만 하면, Pydantic이 나머지를 모두 처리해 줍니다. 예를 들어, API로 들어오는 JSON 요청 본문(Request Body)의 유효성을 검사하고, 특정 필드가 정수인지, 문자열인지, 혹은 유효한 이메일 형식인지를 런타임에 자동으로 확인합니다. 만약 유효하지 않은 데이터가 들어오면, 어떤 필드에서 어떤 오류가 발생했는지 상세한 정보를 담은 422 Unprocessable Entity 응답을 자동으로 생성해 줍니다.
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
from typing import Optional
app = FastAPI()
class User(BaseModel):
username: str
email: EmailStr # 이메일 형식 자동 검증
full_name: Optional[str] = None
age: int
@app.post("/users/")
async def create_user(user: User):
# 이 함수가 실행되기 전에, FastAPI(Pydantic)는
# 요청 본문이 User 모델의 스키마와 일치하는지 자동으로 검증합니다.
# - username, email, age 필드가 모두 존재하는가?
# - email이 유효한 이메일 형식인가?
# - age가 정수인가?
# 만약 검증에 실패하면, 클라이언트에게 상세한 오류 메시지를 반환합니다.
return user
이 두 라이브러리의 조합은 환상적입니다. Starlette가 제공하는 초고속 비동기 처리 능력 위에, Pydantic이 제공하는 강력하고 선언적인 데이터 모델링이 더해져, 개발자는 비즈니스 로직에만 집중하면서도 성능과 안정성을 모두 확보할 수 있게 됩니다.
개발자 경험(DX)의 혁신
FastAPI가 개발자들 사이에서 폭발적인 인기를 얻은 또 다른 이유는 바로 압도적인 개발자 경험(Developer Experience, DX) 때문입니다. Pydantic과 타입 힌트의 적극적인 활용은 다음과 같은 놀라운 부가 가치를 창출합니다.
- 자동 대화형 API 문서: FastAPI는 코드로부터 자동으로 OpenAPI(이전의 Swagger)와 ReDoc 문서를 생성합니다. 개발자는 별도의 문서화 도구나 작업 없이, 그저 코드를 작성하는 것만으로도 최신 API 명세를 얻을 수 있습니다. `/docs` 경로로 접속하면 Swagger UI가, `/redoc` 경로로 접속하면 ReDoc UI가 나타나며, 이 인터페이스를 통해 API 엔드포인트를 직접 테스트해 볼 수도 있습니다. 이는 프론트엔드 개발자와의 협업 효율을 극적으로 향상시킵니다.
- 강력한 편집기 지원: 모든 것이 타입 힌트를 기반으로 하기 때문에, VS Code, PyCharm과 같은 최신 코드 편집기에서 완벽에 가까운 자동 완성(autocompletion)과 타입 검사(type checking)를 지원받을 수 있습니다. 이는 오타로 인한 버그를 사전에 방지하고, 코드의 가독성과 유지보수성을 크게 높여줍니다.
- 간결하고 직관적인 코드: 복잡한 유효성 검사 로직, 데이터 직렬화 로직을 직접 작성할 필요가 없으므로 코드가 매우 간결해집니다. 요청 데이터는 Pydantic 모델 객체로, 의존성은 함수 인자로 깔끔하게 전달받으므로, 개발자는 순수하게 비즈니스 로직을 구현하는 데만 집중할 수 있습니다.
결론적으로, FastAPI는 Node.js나 Go와 같은 타 언어의 고성능 프레임워크와 견주어도 손색없는 실행 속도를 제공하면서도, 파이썬 특유의 쉽고 간결한 문법과 강력한 생태계를 그대로 활용할 수 있게 해줍니다. 이는 개발 속도와 실행 속도라는 두 마리 토끼를 모두 잡으려는 현대 개발팀에게 가장 매력적인 선택지가 될 수밖에 없습니다.
FastAPI 핵심 개념 완전 정복
FastAPI의 강력함은 몇 가지 핵심 개념들의 유기적인 조합에서 비롯됩니다. 이 개념들을 깊이 있게 이해하는 것은 FastAPI를 단순히 '사용하는' 수준을 넘어 '제대로 활용하는' 전문가로 나아가는 첫걸음입니다. 비동기 처리, Pydantic 데이터 모델링, 그리고 의존성 주입 시스템은 FastAPI의 삼위일체와 같습니다. 어느 하나라도 빠지면 그 진가를 발휘하기 어렵습니다.
비동기(Asynchronous)와 `async`/`await`의 실체
앞서 언급했듯이 FastAPI의 성능은 비동기 처리에 기반합니다. 파이썬에서는 `async def`로 비동기 함수(코루틴, Coroutine)를 정의하고, `await` 키워드로 다른 비동기 함수의 실행이 끝날 때까지 기다리면서 이벤트 루프에 제어권을 넘겨줍니다. 이것이 바로 '논블로킹(Non-blocking)'의 핵심입니다.
다음 예제를 통해 동기 코드와 비동기 코드의 근본적인 차이를 이해해 봅시다.
import time
import asyncio
# 동기 함수: time.sleep()은 운영체제의 스레드 자체를 멈춘다.
def sync_blocking_io():
print("동기 작업 시작")
time.sleep(2) # 이 시간 동안 프로세스는 완전히 멈춤
print("동기 작업 완료")
# 비동기 함수 (코루틴): asyncio.sleep()은 이벤트 루프를 멈추지 않는다.
async def async_non_blocking_io():
print("비동기 작업 시작")
await asyncio.sleep(2) # 이 시간 동안 다른 비동기 작업이 실행될 수 있음
print("비동기 작업 완료")
async def main():
start_time = time.time()
# 동기 함수를 그대로 호출하면 코루틴 객체만 반환된다.
# print(async_non_blocking_io()) # <coroutine object async_non_blocking_io at ...>
# 비동기 함수들을 동시에 실행시키기
await asyncio.gather(
async_non_blocking_io(),
async_non_blocking_io(),
async_non_blocking_io()
)
end_time = time.time()
print(f"총 소요 시간: {end_time - start_time:.2f}초")
# 위 main() 함수를 실행하면 3개의 작업이 거의 동시에 시작되고,
# 약 2초 후에 모두 완료된다. 총 소요 시간은 약 2초.
# 만약 sync_blocking_io()를 3번 호출했다면 총 6초가 걸렸을 것이다.
if __name__ == "__main__":
asyncio.run(main())
FastAPI의 경로 처리 함수(Path Operation Function)를 `async def`로 선언하면, FastAPI는 이 함수를 비동기적으로 실행합니다. 만약 데이터베이스 조회, 외부 API 호출 등 I/O 작업이 필요하다면, 반드시 해당 작업을 지원하는 비동기 라이브러리(예: `asyncpg` for PostgreSQL, `httpx` for HTTP requests)를 사용하고 `await` 키워드를 붙여 호출해야 합니다. 만약 비동기 함수 내에서 동기 I/O 함수(`requests.get`, `time.sleep`)를 `await` 없이 호출하면, 비동기의 이점을 전혀 살리지 못하고 전체 이벤트 루프를 막아버리는 최악의 결과를 초래합니다.
중요: 만약 반드시 사용해야 하는 라이브러리가 비동기를 지원하지 않는다면, FastAPI의 `run_in_threadpool`을 사용하여 동기 함수를 별도의 스레드 풀에서 실행함으로써 이벤트 루프가 블로킹되는 것을 막을 수 있습니다.
from fastapi import FastAPI
from fastapi.concurrency import run_in_threadpool
import requests
app = FastAPI()
def blocking_io_call():
# requests는 동기 라이브러리
response = requests.get("https://httpbin.org/delay/2")
return response.json()
@app.get("/")
async def run_blocking_task():
# 동기 함수를 awaitable하게 만들어 이벤트 루프를 막지 않도록 한다.
result = await run_in_threadpool(blocking_io_call)
return result
Pydantic을 이용한 강력한 데이터 모델링
Pydantic은 FastAPI의 개발 생산성을 책임지는 핵심 요소입니다. 단순히 타입 검사를 넘어, 복잡한 데이터 구조를 선언적으로 정의하고, 다양한 유효성 검사 규칙을 적용하며, JSON 직렬화/역직렬화 방식을 세밀하게 제어할 수 있습니다.
요청 데이터(Request Data)와 응답 데이터(Response Data) 분리
실제 애플리케이션에서는 데이터베이스 모델과 API를 통해 노출되는 데이터의 형태가 다른 경우가 많습니다. 예를 들어, 사용자 모델에는 비밀번호 해시값이 저장되어 있지만, API 응답으로 이를 노출해서는 안 됩니다. Pydantic 모델을 여러 개 정의하여 이러한 역할을 명확히 분리하는 것이 좋은 설계입니다.
from pydantic import BaseModel, Field
from typing import Optional
# 데이터베이스 모델(SQLAlchemy 등)과 상호작용할 기본 스키마
class ItemBase(BaseModel):
title: str
description: Optional[str] = None
# 아이템 생성을 위해 클라이언트로부터 받을 데이터 (입력)
# ItemBase를 상속받아 필드를 그대로 사용
class ItemCreate(ItemBase):
pass
# 클라이언트에게 응답으로 보낼 데이터 (출력)
# ItemBase를 상속받고, id와 owner_id 필드를 추가
class Item(ItemBase):
id: int
owner_id: int
# ORM 객체를 Pydantic 모델로 변환할 수 있도록 설정
class Config:
orm_mode = True
위와 같이 `ItemCreate` 모델은 요청 본문을 검증하는 데 사용되고, `Item` 모델은 `@app.get` 데코레이터의 `response_model` 인자로 사용되어 응답 데이터의 형식을 보장하고, 민감한 데이터가 유출되는 것을 방지합니다.
고급 유효성 검사
Pydantic은 기본적인 타입 검사 외에도 `Field`를 사용하여 더 상세한 유효성 검사 규칙을 추가할 수 있습니다.
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str = Field(min_length=3, max_length=50, description="아이템의 이름")
price: float = Field(gt=0, description="가격은 0보다 커야 합니다.")
tax: Optional[float] = Field(None, ge=0, le=1, description="세금은 0과 1 사이의 값이어야 합니다.")
위 코드에서 `gt`는 "greater than", `ge`는 "greater than or equal", `le`는 "less than or equal"을 의미합니다. 이 외에도 정규표현식을 이용한 문자열 패턴 검사, 커스텀 유효성 검사기(validator) 정의 등 강력한 기능을 제공하여, 복잡한 비즈니스 규칙을 모델 계층에서 깔끔하게 처리할 수 있습니다.
의존성 주입(Dependency Injection) 시스템의 이해
의존성 주입(DI)은 FastAPI의 가장 독창적이고 강력한 기능 중 하나입니다. 이는 코드의 재사용성을 높이고, 결합도(coupling)를 낮추며, 테스트를 용이하게 만드는 중요한 소프트웨어 디자인 패턴입니다.
FastAPI에서 의존성은 단순히 '값을 반환하는 함수(또는 다른 callable)'입니다. FastAPI는 경로 처리 함수가 실행되기 전에 이 의존성 함수를 먼저 실행하고, 그 반환값을 경로 처리 함수의 파라미터로 '주입'해줍니다. 이 과정은 `Depends`를 사용하여 선언합니다.
가장 흔한 사용 사례는 데이터베이스 세션을 관리하는 것입니다.
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
# 이 파일들은 실제 프로젝트에서 분리되어야 함
from . import crud, models, schemas
from .database import SessionLocal, engine
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# 의존성(Dependency)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
# db: Session = Depends(get_db)
# 1. FastAPI는 파라미터 db의 타입이 Session이고, 기본값이 Depends(get_db)인 것을 확인.
# 2. get_db 함수를 호출.
# 3. get_db는 SessionLocal()을 통해 새로운 DB 세션을 생성하고 'yield' 함.
# 4. 이 'yield'된 db 객체가 create_user 함수의 'db' 파라미터로 주입됨.
# 5. create_user 함수 내부 로직이 실행됨.
# 6. 함수 실행이 끝나면, get_db의 finally 블록이 실행되어 db.close()가 호출됨.
# (요청 처리 중 예외가 발생해도 finally는 항상 실행되므로 안전하게 세션이 닫힘)
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
의존성 주입 시스템의 장점은 다음과 같습니다:
- 코드 중복 제거: 여러 엔드포인트에서 공통적으로 사용되는 로직(예: DB 세션 관리, 현재 로그인한 사용자 정보 가져오기)을 별도의 의존성 함수로 분리하여 재사용할 수 있습니다.
- 계층화 및 관심사 분리: 비즈니스 로직(경로 처리 함수)과 인프라 관련 로직(DB 연결, 인증 처리)을 분리하여 코드의 구조를 명확하게 만듭니다.
- 쉬운 테스트: 테스트 시 실제 의존성(예: 실제 DB에 연결하는 `get_db`)을 가짜 의존성(예: 테스트용 인메모리 DB에 연결하는 `get_test_db`)으로 손쉽게 교체할 수 있습니다. 이를 통해 외부 환경에 의존하지 않는 빠르고 안정적인 단위 테스트 작성이 가능해집니다.
이 세 가지 핵심 개념, 즉 비동기 처리, Pydantic 모델링, 의존성 주입은 서로 긴밀하게 연결되어 FastAPI를 단순한 웹 프레임워크가 아닌, 고성능과 고생산성을 동시에 달성하는 종합적인 API 개발 플랫폼으로 만들어 줍니다.
실전! CRUD API 서버 구축하기
이론적인 개념을 익혔다면 이제 실제 코드를 통해 이를 구체화할 차례입니다. 데이터베이스와 연동하여 기본적인 생성(Create), 읽기(Read), 갱신(Update), 삭제(Delete) 기능을 제공하는 CRUD API 서버를 구축해 보겠습니다. 이 과정에서 유지보수와 확장이 용이한 프로젝트 구조를 설계하고, 최신 비동기 ORM인 SQLAlchemy 2.0의 `asyncio` 지원을 활용하여 모든 데이터베이스 작업을 비동기적으로 처리할 것입니다.
프로젝트 구조 설계
애플리케이션이 복잡해질수록 잘 설계된 프로젝트 구조는 필수적입니다. 관심사를 분리하는 것은 코드의 가독성을 높이고, 팀원 간의 협업을 원활하게 하며, 향후 기능 추가 및 유지보수를 용이하게 만듭니다. 일반적인 FastAPI 프로젝트 구조는 다음과 같습니다.
/myproject
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI 앱 인스턴스 생성 및 전체 설정
│ ├── crud.py # 데이터베이스 CRUD 로직 함수들
│ ├── database.py # 데이터베이스 연결 및 세션 관리
│ ├── models.py # SQLAlchemy ORM 모델 (테이블 정의)
│ ├── schemas.py # Pydantic 모델 (데이터 스키마)
│ └── routers/
│ ├── __init__.py
│ └── items.py # 'items' 리소스 관련 라우터
│ └── users.py # 'users' 리소스 관련 라우터
│
├── tests/
│ └── ... # 테스트 코드
│
├── .env # 환경 변수 파일
├── requirements.txt # 의존성 패키지 목록
└── ...
이 구조에서 각 파일과 디렉터리의 역할은 명확합니다.
- `main.py`: 애플리케이션의 진입점(entrypoint)입니다. FastAPI 인스턴스를 생성하고, 미들웨어를 설정하며, 각 기능별로 분리된 라우터를 포함(include)합니다.
- `database.py`: 데이터베이스 엔진과 세션 생성기를 설정합니다.
- `models.py`: 데이터베이스 테이블 구조를 파이썬 클래스로 정의합니다.
- `schemas.py`: API의 요청 및 응답 데이터 형태를 Pydantic 모델로 정의합니다.
- `crud.py`: 실제 데이터베이스에 접근하여 데이터를 조작하는 함수들을 모아둡니다. 이 계층을 분리함으로써 라우터는 HTTP 요청 처리와 비즈니스 로직에만 집중할 수 있습니다.
- `routers/`: 기능별(리소스별)로 API 엔드포인트(`@router.get`, `@router.post` 등)를 정의한 `APIRouter`를 모아두는 디렉터리입니다.
비동기 데이터베이스 연동: SQLAlchemy 2.0 AsyncIO
전통적으로 파이썬의 대표적인 ORM인 SQLAlchemy는 동기 방식으로만 작동했습니다. 그러나 버전 1.4부터 실험적으로 도입되고 2.0에서 정식 기능이 된 `asyncio` 지원을 통해, 이제 SQLAlchemy를 사용하여 완전한 비동기 데이터베이스 작업을 수행할 수 있게 되었습니다.
1. `database.py`: 비동기 엔진 및 세션 설정
# app/database.py
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
from sqlalchemy.orm import declarative_base
SQLALCHEMY_DATABASE_URL = "sqlite+aiosqlite:///./test.db" # 비동기 SQLite 드라이버 사용
# 비동기 엔진 생성
engine = create_async_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
# 비동기 세션 생성기
# autocommit=False, autoflush=False는 비동기 환경에서 권장되는 설정
AsyncSessionLocal = async_sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# 의존성 주입을 위한 비동기 DB 세션 getter
async def get_async_db() -> AsyncSession:
async with AsyncSessionLocal() as session:
yield session
`create_async_engine`을 사용하여 비동기 엔진을 만들고, `async_sessionmaker`를 사용하여 비동기 세션을 생성하는 팩토리를 설정합니다. `get_async_db`는 `async with` 구문을 사용하여 요청마다 새로운 세션을 생성하고, 요청 처리가 끝나면 자동으로 세션을 닫아주는 역할을 하는 비동기 제너레이터 의존성입니다.
2. `models.py`: ORM 모델 정의
# app/models.py
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from .database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
items = relationship("Item", back_populates="owner")
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True)
description = Column(String, index=True)
owner_id = Column(Integer, ForeignKey("users.id"))
owner = relationship("User", back_populates="items")
3. `schemas.py`: Pydantic 스키마 정의
# app/schemas.py
from pydantic import BaseModel
from typing import List, Optional
# Item 관련 스키마
class ItemBase(BaseModel):
title: str
description: Optional[str] = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
orm_mode = True
# User 관련 스키마
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool = True # 예시로 추가된 필드
items: List[Item] = []
class Config:
orm_mode = True
4. `crud.py`: 비동기 CRUD 함수 구현
# app/crud.py
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from . import models, schemas
async def get_user(db: AsyncSession, user_id: int):
result = await db.execute(select(models.User).filter(models.User.id == user_id))
return result.scalars().first()
async def create_user_item(db: AsyncSession, item: schemas.ItemCreate, user_id: int):
db_item = models.Item(**item.dict(), owner_id=user_id)
db.add(db_item)
await db.commit()
await db.refresh(db_item)
return db_item
모든 데이터베이스 작업이 `await db.execute(...)`, `await db.commit()`, `await db.refresh()` 와 같이 `await` 키워드를 동반하는 비동기 방식으로 수행되는 것을 확인할 수 있습니다. 이를 통해 데이터베이스 I/O가 발생하는 동안에도 서버는 다른 요청을 처리할 수 있습니다.
라우터(`APIRouter`)를 이용한 엔드포인트 모듈화
모든 엔드포인트를 `main.py`에 작성하면 파일이 금방 비대해지고 관리가 어려워집니다. `APIRouter`를 사용하여 관련 엔드포인트들을 기능별로 그룹화하고 별도의 파일로 분리할 수 있습니다.
1. `routers/items.py`: 아이템 라우터 구현
# app/routers/items.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from typing import List
from .. import crud, schemas
from ..database import get_async_db
router = APIRouter(
prefix="/items", # 이 라우터의 모든 경로 앞에 /items가 붙음
tags=["items"], # API 문서에서 'items' 그룹으로 묶임
responses={404: {"description": "Not found"}}, # 공통 응답 정의
)
@router.post("/", response_model=schemas.Item)
async def create_item_for_user(
user_id: int, item: schemas.ItemCreate, db: AsyncSession = Depends(get_async_db)
):
# 실제로는 user_id를 경로 파라미터나 인증된 사용자 정보에서 가져와야 함
# 여기서는 예시를 위해 쿼리 파라미터로 받음
return await crud.create_user_item(db=db, item=item, user_id=user_id)
2. `main.py`: 라우터 포함
# app/main.py
from fastapi import FastAPI
from .database import engine, Base
from .routers import items, users # users 라우터도 있다고 가정
# 애플리케이션 시작 시 데이터베이스 테이블 생성 (개발 환경용)
@asynccontextmanager
async def lifespan(app: FastAPI):
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
yield
app = FastAPI(lifespan=lifespan)
# 각 라우터를 메인 앱에 포함
app.include_router(users.router)
app.include_router(items.router)
@app.get("/")
async def root():
return {"message": "Hello World"}
이렇게 구조를 분리함으로써 각 파일은 자신의 책임에만 집중하게 됩니다. `items.py`는 아이템 관련 API 로직만, `crud.py`는 데이터베이스 상호작용 로직만 담당합니다. 이러한 설계는 애플리케이션의 규모가 커져도 복잡도를 관리 가능한 수준으로 유지하는 데 결정적인 역할을 합니다.
FastAPI 애플리케이션 고도화
기본적인 CRUD 기능을 구현했다면, 이제 실제 프로덕션 환경에서 요구되는 고급 기능들을 추가하여 애플리케이션을 더욱 견고하고 풍부하게 만들 차례입니다. 미들웨어를 통한 공통 로직 처리, 백그라운드 작업을 이용한 응답성 향상, 그리고 API의 핵심인 인증 및 인가 구현은 모든 실용적인 애플리케이션에 필수적인 요소입니다.
미들웨어(Middleware)를 활용한 공통 로직 처리
미들웨어는 모든(또는 특정) 요청이 엔드포인트에 도달하기 전, 그리고 모든 응답이 클라이언트에게 전송되기 전에 거치는 공통 처리 계층입니다. 이를 통해 다음과 같은 공통 로직을 효율적으로 구현할 수 있습니다.
- 요청 처리 시간 측정
- CORS(Cross-Origin Resource Sharing) 헤더 추가
- Gzip 압축
- 요청 로깅 및 에러 리포팅
- 인증 헤더 검사
FastAPI에서는 `@app.middleware("http")` 데코레이터를 사용하여 간단하게 커스텀 미들웨어를 추가할 수 있습니다. 다음은 모든 요청에 대해 처리 시간을 계산하여 `X-Process-Time` 헤더를 응답에 추가하는 미들웨어의 예시입니다.
import time
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
# 요청 처리 시작 전
start_time = time.time()
# call_next(request)를 통해 다음 미들웨어 또는 실제 경로 처리 함수를 호출
response = await call_next(request)
# 응답 생성 후
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
return response
@app.get("/")
async def main():
await asyncio.sleep(0.5) # 가상 작업
return {"message": "Hello"}
이 미들웨어는 애플리케이션의 모든 엔드포인트에 적용됩니다. 클라이언트가 `/` 경로로 요청을 보내면, 응답 헤더에 `X-Process-Time: 0.500...` 과 같은 값이 포함되어 반환됩니다. 이처럼 미들웨어는 횡단 관심사(cross-cutting concerns)를 애플리케이션의 핵심 비즈니스 로직과 분리하여 관리할 수 있는 강력한 도구입니다.
백그라운드 작업(Background Tasks)으로 응답 시간 단축
사용자에게 즉시 결과를 반환할 필요는 없지만, 요청 처리의 일부로 반드시 실행되어야 하는 작업들이 있습니다. 예를 들어, 회원가입 요청을 처리한 후 환영 이메일을 보내거나, 복잡한 리포트를 생성하여 어딘가에 저장하는 작업 등이 해당됩니다. 이러한 작업들을 동기적으로 처리하면 사용자는 이메일 전송이나 리포트 생성이 완료될 때까지 불필요하게 기다려야 합니다.
FastAPI는 `BackgroundTasks`를 통해 이러한 'fire-and-forget' 스타일의 작업을 매우 쉽게 처리할 수 있도록 지원합니다. `BackgroundTasks`는 응답이 클라이언트에게 전송된 *후에* 같은 프로세스 내에서 실행됩니다.
from fastapi import BackgroundTasks, FastAPI
app = FastAPI()
def write_notification(email: str, message=""):
# 이 함수는 시간이 오래 걸리는 작업(예: 파일 쓰기, 이메일 전송)을 시뮬레이션
with open("log.txt", mode="a") as email_file:
content = f"notification for {email}: {message}\n"
email_file.write(content)
@app.post("/send-notification/{email}")
async def send_notification(email: str, background_tasks: BackgroundTasks):
# 백그라운드 작업 추가
# 응답을 반환하기 전에 함수와 인자만 등록해둔다.
background_tasks.add_task(write_notification, email, message="some notification")
# 이 응답은 write_notification 함수가 끝나기를 기다리지 않고 즉시 반환된다.
return {"message": "Notification sent in the background"}
경로 처리 함수에 `BackgroundTasks` 타입의 파라미터를 추가하고, `background_tasks.add_task()`를 호출하여 실행할 함수와 인자를 전달하면 됩니다. FastAPI는 응답을 반환한 후에 이 작업을 비동기적으로 처리합니다.
주의: `BackgroundTasks`는 간단한 작업에 적합합니다. CPU를 많이 사용하거나, 실패 시 재시도, 작업 모니터링 등이 필요한 무거운 작업의 경우, Celery나 ARQ와 같은 전문적인 분산 작업 큐(Task Queue)를 사용하는 것이 바람직합니다.
인증과 인가: OAuth2와 JWT
대부분의 API는 보호된 리소스에 접근하기 위해 인증(Authentication, 사용자가 누구인지 확인)과 인가(Authorization, 사용자가 무엇을 할 수 있는지 결정) 절차를 필요로 합니다. 현대적인 API에서는 상태를 저장하지 않는(stateless) 방식인 JWT(JSON Web Token)를 사용하는 OAuth2 프로토콜이 사실상의 표준으로 사용됩니다.
FastAPI는 `fastapi.security` 모듈을 통해 OAuth2와 같은 보안 스킴을 쉽게 통합할 수 있는 도구를 제공합니다. 일반적인 JWT 기반 인증 흐름은 다음과 같습니다.
- 사용자가 아이디/비밀번호를 `/token`과 같은 엔드포인트로 전송합니다.
- 서버는 아이디/비밀번호를 검증하고, 유효하다면 사용자를 식별하는 정보(예: user_id)와 만료 시간을 담은 JWT를 생성하여 서명한 후 클라이언트에게 반환합니다.
- 클라이언트는 이 JWT를 저장하고, 이후 보호된 API를 호출할 때마다 `Authorization: Bearer <token>` 헤더에 담아 전송합니다.
- 서버는 요청 헤더의 JWT를 검증(서명 확인, 만료 시간 확인)하고, 유효하다면 토큰에 담긴 사용자 정보를 바탕으로 요청을 처리합니다.
다음은 이 흐름을 구현한 예시 코드입니다. (실제로는 비밀번호 해싱, JWT 시크릿 키 관리 등 추가적인 보안 조치가 필요합니다.)
# 이 예시는 개념 설명을 위한 것으로, 실제 프로덕션 코드는 더 복잡합니다.
# passlib, python-jose 라이브러리 설치 필요: pip install passlib[bcrypt] python-jose
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
from jose import JWTError, jwt
from datetime import datetime, timedelta
# --- JWT 설정 ---
SECRET_KEY = "YOUR_SECRET_KEY" # 실제로는 환경 변수에서 불러와야 함
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# --- Pydantic 모델 ---
class TokenData(BaseModel):
username: str | None = None
# --- 보안 관련 의존성 ---
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# --- 유틸리티 함수 ---
def create_access_token(data: dict, expires_delta: timedelta | None = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# --- 의존성: 현재 사용자 정보 가져오기 ---
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
except JWTError:
raise credentials_exception
# 실제로는 DB에서 사용자 정보를 조회해야 함
# user = get_user_from_db(username=token_data.username)
# if user is None:
# raise credentials_exception
return {"username": token_data.username} # 예시로 dict 반환
# --- FastAPI 앱 ---
app = FastAPI()
@app.post("/token")
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
# 실제로는 DB에서 사용자 검증 및 비밀번호 해시 비교
if not (form_data.username == "johndoe" and form_data.password == "secret"):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": form_data.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/users/me")
async def read_users_me(current_user: dict = Depends(get_current_user)):
# get_current_user 의존성이 먼저 실행되어 토큰을 검증하고 사용자 정보를 반환한다.
# 검증에 실패하면 이 함수는 실행되지 않고 401 오류가 발생한다.
return current_user
`get_current_user`와 같은 의존성을 만들어두면, 보호가 필요한 모든 엔드포인트에 `Depends(get_current_user)`를 추가하는 것만으로 간단하게 인증 및 인가 로직을 적용할 수 있습니다. 이는 의존성 주입 시스템의 강력함을 보여주는 또 다른 좋은 예시입니다.
프로덕션 환경을 위한 배포 및 운영
훌륭한 애플리케이션을 개발하는 것만큼이나 중요한 것은 이를 안정적으로 배포하고 운영하는 것입니다. FastAPI 애플리케이션을 프로덕션 환경에 배포하기 위해서는 ASGI 서버, 프로세스 매니저, 그리고 컨테이너화와 같은 기술들을 이해하고 적절히 활용해야 합니다.
ASGI 서버: Uvicorn과 Gunicorn
FastAPI 자체는 웹 프레임워크일 뿐, 실제 HTTP 요청을 받아 처리하는 서버가 아닙니다. 개발 중에 `uvicorn main:app --reload` 명령어를 사용하는 것은 Uvicorn이라는 ASGI 서버를 실행하는 것입니다. Uvicorn은 Starlette와 마찬가지로 `asyncio`를 기반으로 만들어진 초고속 ASGI 서버로, FastAPI 애플리케이션을 실행하는 데 가장 이상적인 선택입니다.
하지만 프로덕션 환경에서는 단일 Uvicorn 프로세스만 실행하는 것만으로는 부족합니다. 다음과 같은 문제들이 발생할 수 있습니다.
- 해당 프로세스에 오류가 발생하면 서버 전체가 다운됩니다.
- 멀티코어 CPU의 이점을 제대로 활용하지 못합니다.
- 서버 재시작 시 다운타임(downtime)이 발생합니다.
이러한 문제들을 해결하기 위해 Gunicorn과 같은 프로세스 매니저를 함께 사용합니다. Gunicorn은 여러 개의 워커(worker) 프로세스를 생성하고 관리하는 역할을 합니다. 각 워커 프로세스는 Uvicorn을 실행하여 FastAPI 애플리케이션을 호스팅합니다. Gunicorn이 마스터 프로세스로서 워커들의 상태를 감시하고, 죽은 워커가 있으면 자동으로 재시작해주며, 다운타임 없는 재배포(graceful restart)를 지원합니다.
Gunicorn과 Uvicorn을 함께 사용하는 일반적인 명령어는 다음과 같습니다.
gunicorn -w 4 -k uvicorn.workers.UvicornWorker myproject.app.main:app
- `gunicorn`: Gunicorn 프로세스 매니저를 실행합니다.
- `-w 4`: 4개의 워커 프로세스를 생성합니다. 일반적으로 `(2 * CPU 코어 수) + 1`을 권장값으로 사용합니다.
- `-k uvicorn.workers.UvicornWorker`: 각 워커 프로세스가 사용할 클래스를 지정합니다. 여기서는 Uvicorn의 워커 클래스를 사용하여 Gunicorn이 Uvicorn을 제어할 수 있도록 합니다.
- `myproject.app.main:app`: 애플리케이션의 위치를 지정합니다. (`myproject/app/main.py` 파일 안에 있는 `app` 객체)
이 조합을 통해 멀티코어 CPU를 최대한 활용하여 동시 처리량을 높이고, 개별 워커의 장애가 전체 서비스의 중단으로 이어지지 않도록 안정성을 확보할 수 있습니다.
Docker를 이용한 컨테이너화
컨테이너화는 애플리케이션과 그 모든 의존성을 격리된 환경인 '컨테이너'에 패키징하는 기술입니다. Docker는 가장 널리 사용되는 컨테이너화 플랫폼으로, 다음과 같은 장점을 제공합니다.
- 환경 일관성: 개발자의 로컬 머신, 테스트 서버, 프로덕션 서버 어디서든 동일한 환경에서 애플리케이션을 실행할 수 있어 "제 컴퓨터에서는 됐는데..."와 같은 문제를 원천적으로 방지합니다.
- 배포 용이성: 복잡한 설치 과정 없이 Docker 이미지를 가져와 실행하는 것만으로 배포가 완료됩니다.
- 확장성: 필요에 따라 컨테이너 수를 늘리거나 줄이는 스케일링이 매우 용이합니다. Kubernetes와 같은 컨테이너 오케스트레이션 도구와 함께 사용하면 더욱 강력한 확장성을 확보할 수 있습니다.
FastAPI 애플리케이션을 위한 프로덕션용 `Dockerfile` 예시는 다음과 같습니다.
# 1. 베이스 이미지 선택
# 슬림 버전을 사용하여 이미지 크기를 최소화
FROM python:3.11-slim
# 2. 작업 디렉토리 설정
WORKDIR /code
# 3. 환경 변수 설정
# 파이썬이 .pyc 파일을 생성하지 않도록 하고, 버퍼링 없이 즉시 출력하도록 설정
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# 4. 의존성 설치
# 먼저 requirements.txt만 복사하여 설치
# -> 소스 코드가 변경될 때마다 매번 의존성을 재설치하는 것을 방지 (Docker 캐시 활용)
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
# 5. 애플리케이션 소스 코드 복사
COPY ./app /code/app
# 6. 서버 실행
# Gunicorn을 사용하여 Uvicorn 워커로 FastAPI 앱 실행
# 0.0.0.0으로 바인딩하여 컨테이너 외부에서의 접근을 허용
CMD ["gunicorn", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "app.main:app", "--bind", "0.0.0.0:8000"]
이 `Dockerfile`을 프로젝트 루트에 위치시키고 `docker build -t my-fastapi-app .` 명령어로 이미지를 빌드한 후, `docker run -d -p 8000:8000 my-fastapi-app` 명령어로 컨테이너를 실행하면, 로컬 8000번 포트로 FastAPI 애플리케이션에 접근할 수 있습니다.
성능 최적화 고려사항
FastAPI는 기본적으로 매우 빠르지만, 애플리케이션의 구조나 코드 작성 방식에 따라 성능이 저하될 수 있습니다. 프로덕션 환경에서 최상의 성능을 유지하기 위해 다음 사항들을 고려해야 합니다.
- 비동기 라이브러리 일관성 유지: 앞서 강조했듯이, `async def` 함수 내에서 동기 블로킹 I/O 라이브러리(예: `requests`)를 사용하면 모든 비동기 처리의 이점이 사라집니다. 외부 HTTP 요청에는 `httpx`를, 데이터베이스 작업에는 비동기를 지원하는 드라이버(`asyncpg`, `aiomysql`, `aiosqlite`)와 ORM(`SQLAlchemy asyncio`, `Tortoise ORM`)을 일관되게 사용해야 합니다.
- 데이터베이스 커넥션 풀링: 데이터베이스 연결을 맺고 끊는 과정은 비용이 큰 작업입니다. 프로덕션 환경에서는 SQLAlchemy의 커넥션 풀 설정을 적절히 조절하여, 미리 생성된 커넥션을 재사용함으로써 오버헤드를 줄여야 합니다. 비동기 엔진 생성 시 `pool_size`, `max_overflow`와 같은 파라미터를 조정할 수 있습니다.
- 응답 모델(`response_model`) 적극 활용: `response_model`을 지정하면 FastAPI는 ORM 객체나 복잡한 내부 데이터 구조를 Pydantic 모델에 정의된 필드만으로 필터링하고 직렬화합니다. 이는 불필요한 데이터가 클라이언트로 전송되는 것을 막고, 데이터베이스 쿼리 시 `lazy loading`으로 인한 추가적인 쿼리가 발생하는 것을 방지하는 데 도움이 됩니다.
- 캐싱 전략 도입: 자주 요청되지만 내용이 자주 바뀌지 않는 데이터(예: 상품 목록, 설정 정보)는 Redis나 Memcached와 같은 인메모리 캐시를 도입하여 응답 시간을 획기적으로 단축할 수 있습니다. `fastapi-cache2`와 같은 라이브러리를 사용하면 데코레이터 기반으로 간단하게 캐싱을 적용할 수 있습니다.
- CPU 바운드 작업 분리: 이미지 처리, 데이터 압축, 복잡한 계산 등 CPU를 많이 사용하는 작업은 비동기 이벤트 루프 내에서 직접 처리하면 안 됩니다. 이러한 작업은 이벤트 루프를 블로킹하여 전체 애플리케이션의 반응성을 떨어뜨립니다. `run_in_threadpool`을 사용하거나, Celery와 같은 별도의 워커 프로세스로 작업을 위임하여 처리해야 합니다.
이러한 배포 및 운영 전략을 통해 FastAPI 애플리케이션의 잠재력을 최대한 이끌어내고, 수많은 동시 요청을 안정적으로 처리하는 고성능 API 서버를 구축할 수 있습니다.
결론: FastAPI, 현대적 API 개발의 새로운 표준
지금까지 우리는 FastAPI가 왜 현대적인 Python API 개발에 있어 강력한 대안으로 떠올랐는지, 그 핵심 철학과 기술적 기반을 깊이 있게 살펴보았습니다. Starlette의 비동기 성능과 Pydantic의 데이터 중심 설계, 그리고 이 둘을 유기적으로 엮어낸 의존성 주입 시스템은 FastAPI를 단순한 웹 프레임워크를 넘어, 개발 생산성과 애플리케이션 성능이라는 두 가지 목표를 동시에 달성하는 종합적인 개발 플랫폼으로 만들었습니다.
우리는 동기와 비동기(Asynchronous) 패러다임의 근본적인 차이에서 출발하여, 타입 힌트를 기반으로 한 자동 유효성 검사와 문서화가 어떻게 개발자 경험을 혁신하는지 확인했습니다. 또한, 체계적인 프로젝트 구조 설계, SQLAlchemy 2.0을 활용한 완전한 비동기 데이터베이스 연동, 그리고 미들웨어, 백그라운드 작업, JWT 인증과 같은 고급 기능을 통해 실용적이고 견고한 애플리케이션을 구축하는 과정을 단계별로 살펴보았습니다. 마지막으로, Gunicorn과 Docker를 활용한 프로덕션 배포 전략은 우리가 만든 애플리케이션을 세상에 안정적으로 선보일 수 있는 마지막 퍼즐 조각이었습니다.
FastAPI를 선택한다는 것은 단순히 '더 빠른' 코드를 작성하는 것을 의미하지 않습니다. 그것은 더 명확하고, 더 안정적이며, 더 유지보수하기 쉬운 코드를 작성하는 새로운 방식에 동참하는 것입니다. 버그는 컴파일 타임에 가까운 개발 단계에서 포착되고, 문서는 코드와 항상 일치하며, 복잡한 로직은 테스트하기 쉬운 작은 단위로 분리됩니다. 이는 개발자가 비즈니스 가치를 창출하는 핵심 로직에 더 많은 시간을 쏟을 수 있게 해주는 선순환 구조를 만듭니다.
물론 FastAPI가 모든 문제에 대한 만병통치약은 아닙니다. 거대한 규모의 모놀리식 애플리케이션이나 관리자 페이지 중심의 프로젝트에는 여전히 Django가 더 적합할 수 있습니다. 그러나 마이크로서비스 아키텍처(MSA)가 대세가 되고, 실시간 데이터 처리와 높은 동시성이 요구되는 오늘날의 웹 환경에서, 고성능 API 서버를 구축하기 위한 선택지로서 FastAPI는 가장 빛나는 별 중 하나임이 분명합니다. 이 글이 여러분의 다음 프로젝트에 FastAPI라는 강력한 도구를 자신 있게 도입하는 데 훌륭한 길잡이가 되었기를 바랍니다.
0 개의 댓글:
Post a Comment