Skip to content

Wiki_BE_Fastapi

openLeeWorld edited this page May 14, 2026 · 3 revisions

FastAPI 기초: 백엔드 개발자 실무 가이드

본 문서는 백엔드 개발에 투입되는 신입 개발자를 위해 작성되었습니다.
FastAPI의 핵심 구성 요소와 실무 패턴을 코드 예시 중심으로 정리합니다.


목차

  1. FastAPI란?
  2. 설치 및 프로젝트 구조
  3. 라우팅 (Routing)
  4. 데이터 검증 및 직렬화 (Pydantic)
  5. 의존성 주입 (Dependency Injection)
  6. 비동기 처리 (Async)
  7. 미들웨어 (Middleware)
  8. 에러 처리
  9. 실무 설정 예시 모음

1. FastAPI란?

FastAPI는 Python 기반의 고성능 웹 프레임워크입니다. async/await 기반 비동기 처리와 Pydantic을 이용한 자동 데이터 검증, 자동 API 문서 생성이 핵심 강점입니다.

클라이언트 요청
      │
      ▼
  미들웨어 (Middleware)        ← 인증, 로깅, CORS 등
      │
      ▼
  라우터 (Router)             ← URL 경로 매핑
      │
      ▼
  의존성 주입 (Depends)       ← DB 세션, 인증 정보 등 공통 로직
      │
      ▼
  핸들러 함수 (Endpoint)      ← 비즈니스 로직 처리
      │
      ▼
  Pydantic 직렬화             ← 응답 데이터 검증 및 변환
      │
      ▼
  HTTP 응답 반환

자동 API 문서 (실행 후 브라우저에서 확인):

URL 설명
http://localhost:8000/docs Swagger UI (대화형 테스트 가능)
http://localhost:8000/redoc ReDoc 문서

2. 설치 및 프로젝트 구조

2.1 설치

# FastAPI 및 ASGI 서버 설치
pip install fastapi uvicorn

# DB 연동 시 추가 설치
pip install sqlalchemy psycopg2-binary   # PostgreSQL, 저희 프로젝트 mysql, asyncmy랑 좀 다릅니다.
pip install python-dotenv                # 환경변수 관리, pydantic-settings이 환경변수 로드에서 표준적으로 바뀜

2.2 서버 실행

# 개발 서버 실행 (--reload: 코드 변경 시 자동 재시작)
uvicorn main:app --reload

# 호스트/포트 지정
uvicorn main:app --host 0.0.0.0 --port 8000 --reload

# 프로덕션 (reload 제거, workers 추가)
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4

2.3 권장 프로젝트 구조

myapp/
├── main.py               ← FastAPI 앱 진입점
├── database.py           ← DB 연결 설정
├── models.py             ← SQLAlchemy ORM 모델
├── schemas.py            ← Pydantic 스키마 (요청/응답 형식)
├── dependencies.py       ← 공통 의존성 함수
├── routers/              ← 도메인별 라우터 분리
│   ├── users.py
│   └── posts.py
└── .env                  ← 환경변수 (DB URL, SECRET KEY 등)

3. 라우팅 (Routing)

3.1 기본 엔드포인트

from fastapi import FastAPI

app = FastAPI()

# GET 요청
@app.get("/")
def read_root():
    return {"message": "Hello World"}

# POST 요청
@app.post("/users")
def create_user():
    return {"message": "created"}

# PUT, PATCH, DELETE
@app.put("/users/{user_id}")
def update_user(user_id: int):
    return {"user_id": user_id}

@app.delete("/users/{user_id}")
def delete_user(user_id: int):
    return {"message": "deleted"}

3.2 경로 파라미터와 쿼리 파라미터

# 경로 파라미터: URL 경로에 포함 ({변수명})
@app.get("/users/{user_id}")
def get_user(user_id: int):          # int 타입 자동 검증
    return {"user_id": user_id}

# 쿼리 파라미터: URL 뒤에 ?key=value 형태
# GET /users?page=1&limit=20&is_active=true
@app.get("/users")
def list_users(
    page: int = 1,           # 기본값 있음 (선택 파라미터)
    limit: int = 20,
    is_active: bool = True,
    search: str | None = None   # None 기본값 = 선택 파라미터
):
    return {"page": page, "limit": limit}

3.3 라우터 분리 (APIRouter)

규모가 커지면 도메인별로 라우터를 분리합니다.

# routers/users.py
from fastapi import APIRouter

router = APIRouter(
    prefix="/users",         # 이 라우터의 모든 경로에 /users 접두사 적용
    tags=["users"],          # Swagger 문서 그룹화
)

@router.get("/")             # 실제 경로: GET /users/
def list_users():
    return []

@router.get("/{user_id}")    # 실제 경로: GET /users/{user_id}
def get_user(user_id: int):
    return {"user_id": user_id}
# main.py
from fastapi import FastAPI
from routers import users, posts

app = FastAPI()

app.include_router(users.router)   # /users 라우터 등록
app.include_router(posts.router)   # /posts 라우터 등록

4. 데이터 검증 및 직렬화 (Pydantic)

FastAPI는 Pydantic 모델로 요청/응답 데이터를 자동 검증합니다. 타입이 맞지 않으면 자동으로 422 에러를 반환합니다.

4.1 스키마 정의

# schemas.py
from pydantic import BaseModel, EmailStr, Field
from datetime import datetime

# 요청 스키마 (클라이언트 → 서버)
class UserCreate(BaseModel):
    name: str = Field(..., min_length=2, max_length=50)   # 필수, 길이 제한
    email: EmailStr                                        # 이메일 형식 자동 검증
    age: int = Field(..., ge=0, le=150)                   # 0 이상 150 이하

# 응답 스키마 (서버 → 클라이언트)
class UserResponse(BaseModel):
    id: int
    name: str
    email: str
    created_at: datetime

    class Config:
        from_attributes = True   # ORM 모델 → Pydantic 변환 허용

4.2 스키마 적용

from schemas import UserCreate, UserResponse

@app.post(
    "/users",
    response_model=UserResponse,   # 응답 형식 지정 (불필요한 필드 자동 제거)
    status_code=201
)
def create_user(user: UserCreate):   # 요청 바디를 UserCreate로 자동 파싱 및 검증
    # user.name, user.email 사용 가능
    # DB 저장 로직...
    return created_user   # UserResponse 형식으로 자동 직렬화

요청 예시:

POST /users
{
  "name": "홍길동",
  "email": "hong@example.com",
  "age": 30
}

검증 실패 시 FastAPI가 자동 반환하는 에러:

HTTP 422 Unprocessable Entity
{
  "detail": [
    {
      "loc": ["body", "email"],
      "msg": "value is not a valid email address",
      "type": "value_error.email"
    }
  ]
}

5. 의존성 주입 (Dependency Injection)

Depends를 사용해 여러 엔드포인트에서 공통 로직(DB 세션, 인증 등)을 재사용합니다.

5.1 DB 세션 주입 (가장 흔한 패턴)

# database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session

DATABASE_URL = "postgresql://user:password@localhost/dbname"

engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(bind=engine)

def get_db():
    db = SessionLocal()
    try:
        yield db          # 엔드포인트에 DB 세션 제공
    finally:
        db.close()        # 요청 완료 후 반드시 세션 닫음
# routers/users.py
from fastapi import Depends
from sqlalchemy.orm import Session
from database import get_db

@router.get("/{user_id}")
def get_user(
    user_id: int,
    db: Session = Depends(get_db)   # DB 세션 자동 주입
):
    user = db.query(User).filter(User.id == user_id).first()
    return user

5.2 인증 의존성

# dependencies.py
from fastapi import Depends, HTTPException, Header

def get_current_user(authorization: str = Header(...)):
    token = authorization.replace("Bearer ", "")
    user = verify_token(token)   # 토큰 검증 로직
    if not user:
        raise HTTPException(status_code=401, detail="인증이 필요합니다.")
    return user

# 엔드포인트에 적용
@router.get("/me")
def get_my_info(current_user = Depends(get_current_user)):
    return current_user

# 라우터 전체에 적용
router = APIRouter(
    prefix="/admin",
    dependencies=[Depends(get_current_user)]   # 이 라우터의 모든 경로에 인증 적용
)

6. 비동기 처리 (Async)

FastAPI는 async def와 def 모두 지원합니다.

import asyncio
import httpx

# async def: I/O 대기 중 다른 요청 처리 가능 (권장)
@app.get("/async-example")
async def async_endpoint():
    async with httpx.AsyncClient() as client:
        response = await client.get("https://api.example.com/data")
    return response.json()

# def: 동기 함수. FastAPI가 스레드풀에서 실행 (블로킹 I/O에 사용)
@app.get("/sync-example")
def sync_endpoint():
    # 동기 DB 조회, 동기 외부 API 호출 등
    return {"data": "result"}

언제 async def를 쓸까:

상황 권장
비동기 DB 라이브러리 사용 (asyncpg, databases) async def
비동기 HTTP 요청 (httpx, aiohttp) async def
SQLAlchemy 동기 세션 사용 def
CPU 집약적 작업 (계산, 이미지 처리) def

실무 팁: SQLAlchemy 동기 세션(SessionLocal)을 쓰면서 async def를 사용하면 블로킹이 발생합니다. 비동기 DB가 필요하면 SQLAlchemy 2.0 async 또는 databases 라이브러리를 사용하십시오.


7. 미들웨어 (Middleware)

모든 요청/응답을 가로채어 공통 로직을 처리합니다.

7.1 CORS 미들웨어 (프론트엔드 연동 필수)

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://example.com", "http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],     # GET, POST, PUT, DELETE 등 모두 허용
    allow_headers=["*"],     # Authorization, Content-Type 등 모두 허용
)

7.2 커스텀 미들웨어 (요청 시간 로깅)

import time
from fastapi import Request

@app.middleware("http")
async def log_request_time(request: Request, call_next):
    start = time.time()

    response = await call_next(request)   # 다음 단계로 요청 전달

    duration = time.time() - start
    print(f"{request.method} {request.url.path} - {duration:.3f}s")

    response.headers["X-Process-Time"] = str(duration)
    return response

8. 에러 처리

8.1 HTTPException

from fastapi import HTTPException

@app.get("/users/{user_id}")
def get_user(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()

    if not user:
        raise HTTPException(
            status_code=404,
            detail="해당 유저를 찾을 수 없습니다."
        )

    return user

8.2 전역 예외 핸들러

from fastapi import Request
from fastapi.responses import JSONResponse

# 특정 예외 타입에 대한 전역 처리
@app.exception_handler(ValueError)
async def value_error_handler(request: Request, exc: ValueError):
    return JSONResponse(
        status_code=400,
        content={"success": False, "error": str(exc)}
    )

# 처리되지 않은 모든 예외 처리 (500 에러 핸들링)
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
    # 실제 에러는 로깅 시스템에 기록
    print(f"Unhandled error: {exc}")
    return JSONResponse(
        status_code=500,
        content={"success": False, "error": "서버 내부 오류가 발생했습니다."}
    )

9. 실무 설정 예시 모음

9.1 환경변수 관리

# .env 파일
DATABASE_URL=postgresql://user:password@localhost/mydb
SECRET_KEY=your-secret-key-here
DEBUG=false
# config.py
from pydantic_settings import BaseSettings   # pip install pydantic-settings

class Settings(BaseSettings):
    database_url: str
    secret_key: str
    debug: bool = False

    class Config:
        env_file = ".env"

settings = Settings()   # .env 파일 자동 로드

# 사용
print(settings.database_url)

9.2 응답 형식 통일

# schemas.py
from typing import TypeVar, Generic
from pydantic import BaseModel

T = TypeVar("T")

class ApiResponse(BaseModel, Generic[T]):
    success: bool = True
    data: T | None = None
    message: str = ""

# 사용
@app.get("/users/{user_id}", response_model=ApiResponse[UserResponse])
def get_user(user_id: int):
    user = ...   # DB 조회
    return ApiResponse(data=user)

# 응답 예시
# { "success": true, "data": { "id": 1, "name": "홍길동" }, "message": "" }

9.3 완성된 main.py 예시

# main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from routers import users, posts
from database import engine, Base

# 테이블 자동 생성 (개발용, 프로덕션은 Alembic 마이그레이션 사용)
Base.metadata.create_all(bind=engine)

app = FastAPI(
    title="My API",
    version="1.0.0",
    docs_url="/docs",       # Swagger UI 경로
    redoc_url="/redoc",
)

# CORS 설정
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 라우터 등록
app.include_router(users.router, prefix="/api/v1")
app.include_router(posts.router, prefix="/api/v1")

@app.get("/health")
def health_check():
    return {"status": "ok"}

빠른 참조 (Quick Reference)

HTTP 메서드 데코레이터

@app.get("/path")      # 조회
@app.post("/path")     # 생성
@app.put("/path")      # 전체 수정
@app.patch("/path")    # 부분 수정
@app.delete("/path")   # 삭제

파라미터 타입 요약

종류 위치 선언 방법
경로 파라미터 /users/{id} 함수 인자와 동일 이름
쿼리 파라미터 /users?page=1 기본값 있는 함수 인자
요청 바디 Request Body Pydantic BaseModel
헤더 Header Header(...)
쿠키 Cookie Cookie(...)

Pydantic Field 검증 옵션

Field(..., min_length=1, max_length=100)   # 문자열 길이
Field(..., ge=0, le=100)                   # 숫자 범위 (ge: >=, le: <=)
Field(..., regex=r"^\d{10,11}$")           # 정규식
Field(default="기본값")                    # 기본값 설정

서버 실행 명령어 요약

uvicorn main:app --reload                   # 개발
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4  # 프로덕션

실무 팁:
DB 스키마 변경 이력 관리는 Base.metadata.create_all() 대신 Alembic 마이그레이션을 사용하십시오. 프로덕션 환경에서 테이블을 안전하게 변경할 수 있습니다 (pip install alembic).


참고 자료

Clone this wiki locally