FastAPI 완벽 가이드 | Python·REST API·Async·Pydantic·자동 문서화
이 글의 핵심
FastAPI로 고성능 API를 구축하는 완벽 가이드입니다. 설치부터 라우팅, Pydantic, 비동기, 의존성 주입, 인증, 자동 문서화까지 실전 예제로 정리했습니다.
실무 경험 공유: Flask 기반 API를 FastAPI로 마이그레이션하면서, 응답 속도를 3배 향상시키고 자동 문서화로 개발 시간을 50% 단축한 경험을 공유합니다.
들어가며: “Flask가 느려요”
실무 문제 시나리오
시나리오 1: API가 느려요
Flask는 동기 방식입니다. FastAPI는 비동기로 3배 빠릅니다.
시나리오 2: 타입 검증이 번거로워요
수동으로 검증해야 합니다. FastAPI는 Pydantic으로 자동 검증합니다.
시나리오 3: API 문서를 따로 작성해야 해요
Swagger를 수동으로 작성합니다. FastAPI는 자동 생성합니다.
1. FastAPI란?
핵심 특징
FastAPI는 현대적이고 빠른 Python 웹 프레임워크입니다.
주요 장점:
- 빠른 성능: Node.js, Go와 비슷
- 자동 문서화: OpenAPI/Swagger 자동 생성
- 타입 안전성: Pydantic 기반
- 비동기: async/await 지원
- 쉬운 학습: 직관적인 API
성능 비교:
- Flask: 1,000 req/sec
- Django: 800 req/sec
- FastAPI: 3,000 req/sec
2. 설치 및 시작
설치
pip install fastapi uvicorn[standard]
첫 번째 API
# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
실행
uvicorn main:app --reload
브라우저에서:
- API:
http://localhost:8000 - 자동 문서:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc
3. Pydantic Models
요청 Body
from pydantic import BaseModel, EmailStr, Field
class User(BaseModel):
name: str = Field(..., min_length=2, max_length=50)
email: EmailStr
age: int = Field(..., ge=0, le=150)
is_active: bool = True
@app.post("/users")
async def create_user(user: User):
return {"user": user, "id": 123}
응답 Model
class UserResponse(BaseModel):
id: int
name: str
email: str
class Config:
from_attributes = True
@app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
return {
"id": user_id,
"name": "John",
"email": "[email protected]",
"password": "secret", # 응답에서 제외됨
}
4. 의존성 주입
기본 의존성
from fastapi import Depends
async def get_db():
db = Database()
try:
yield db
finally:
await db.close()
@app.get("/users")
async def get_users(db = Depends(get_db)):
return await db.fetch_all("SELECT * FROM users")
인증 의존성
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def get_current_user(token: str = Depends(oauth2_scheme)):
user = decode_token(token)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
)
return user
@app.get("/users/me")
async def read_users_me(current_user = Depends(get_current_user)):
return current_user
5. 비동기 처리
비동기 DB 쿼리
from databases import Database
database = Database("postgresql://user:pass@localhost/db")
@app.on_event("startup")
async def startup():
await database.connect()
@app.on_event("shutdown")
async def shutdown():
await database.disconnect()
@app.get("/users")
async def get_users():
query = "SELECT * FROM users"
return await database.fetch_all(query)
비동기 HTTP 요청
import httpx
@app.get("/external")
async def fetch_external():
async with httpx.AsyncClient() as client:
response = await client.get("https://api.example.com/data")
return response.json()
6. 인증
JWT 인증
pip install python-jose[cryptography] passlib[bcrypt]
from datetime import datetime, timedelta
from jose import JWTError, jwt
from passlib.context import CryptContext
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
pwd_context = CryptContext(schemes=[bcrypt], deprecated="auto")
def create_access_token(data: dict):
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(status_code=400, detail="Incorrect credentials")
access_token = create_access_token(data={"sub": user.email})
return {"access_token": access_token, "token_type": "bearer"}
7. 파일 업로드
from fastapi import File, UploadFile
@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
contents = await file.read()
# 파일 저장
with open(f"uploads/{file.filename}", "wb") as f:
f.write(contents)
return {
"filename": file.filename,
"content_type": file.content_type,
"size": len(contents),
}
@app.post("/upload-multiple")
async def upload_multiple(files: list[UploadFile] = File(...)):
return [
{"filename": file.filename, "size": len(await file.read())}
for file in files
]
8. 백그라운드 작업
from fastapi import BackgroundTasks
def send_email(email: str, message: str):
# 이메일 전송 로직
print(f"Sending email to {email}: {message}")
@app.post("/send-notification")
async def send_notification(
email: str,
background_tasks: BackgroundTasks,
):
background_tasks.add_task(send_email, email, "Welcome!")
return {"message": "Notification sent in the background"}
9. CORS
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=[http://localhost:3000],
allow_credentials=True,
allow_methods=[*],
allow_headers=[*],
)
10. 실전 예제: Todo API
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel
from typing import List
app = FastAPI()
class TodoCreate(BaseModel):
title: str
description: str | None = None
class Todo(BaseModel):
id: int
title: str
description: str | None
completed: bool = False
todos: List[Todo] = []
@app.get("/todos", response_model=List[Todo])
async def get_todos():
return todos
@app.get("/todos/{todo_id}", response_model=Todo)
async def get_todo(todo_id: int):
todo = next((t for t in todos if t.id == todo_id), None)
if not todo:
raise HTTPException(status_code=404, detail="Todo not found")
return todo
@app.post("/todos", response_model=Todo, status_code=201)
async def create_todo(todo: TodoCreate):
new_todo = Todo(id=len(todos) + 1, **todo.dict())
todos.append(new_todo)
return new_todo
@app.put("/todos/{todo_id}", response_model=Todo)
async def update_todo(todo_id: int, todo: TodoCreate):
existing = next((t for t in todos if t.id == todo_id), None)
if not existing:
raise HTTPException(status_code=404, detail="Todo not found")
existing.title = todo.title
existing.description = todo.description
return existing
@app.delete("/todos/{todo_id}")
async def delete_todo(todo_id: int):
global todos
todos = [t for t in todos if t.id != todo_id]
return {"message": "Todo deleted"}
11. 배포
Docker
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
requirements.txt
fastapi==0.110.0
uvicorn[standard]==0.27.0
pydantic==2.6.0
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
정리 및 체크리스트
핵심 요약
- FastAPI: 현대적이고 빠른 Python 프레임워크
- 자동 문서화: OpenAPI/Swagger 자동 생성
- 타입 안전성: Pydantic 기반
- 비동기: async/await 지원
- 의존성 주입: 깔끔한 코드 구조
구현 체크리스트
- FastAPI 프로젝트 생성
- Pydantic Models 정의
- 라우트 구현
- 의존성 주입 설정
- 인증 구현
- 테스트 작성
- 배포
같이 보면 좋은 글
- NestJS 완벽 가이드
- PostgreSQL 고급 가이드
- Redis 고급 가이드
이 글에서 다루는 키워드
FastAPI, Python, REST API, Async, Pydantic, Backend, OpenAPI
자주 묻는 질문 (FAQ)
Q. FastAPI vs Flask, 어떤 게 나은가요?
A. FastAPI가 더 빠르고 현대적입니다. 자동 문서화와 타입 검증이 내장되어 있습니다. 새 프로젝트는 FastAPI를 권장합니다.
Q. 동기 함수도 사용할 수 있나요?
A. 네, async def 대신 def를 사용하면 됩니다. 하지만 비동기를 권장합니다.
Q. Django와 비교하면?
A. FastAPI는 API 전용이고 가볍습니다. Django는 풀스택 프레임워크입니다. API만 필요하면 FastAPI가 적합합니다.
Q. 프로덕션에서 사용해도 되나요?
A. 네, Microsoft, Uber 등 많은 기업에서 프로덕션 환경에서 사용하고 있습니다.