목차
22 장

FastAPI 시작과 셋업

왜 FastAPI인가, uv로 첫 프로젝트 셋업, Hello FastAPI, OpenAPI/Swagger UI 자동 생성까지 한곳에 정리합니다.

4부 실전 FastAPI의 시작입니다. 1부 (입문) → 2부 (구조화) → 3부 (깊이 · 동시성)의 21장이 다 들어갈 무대입니다. FastAPI로 한 프로젝트를 만들면서 타입 힌트, dataclass / Pydantic, async / await, 의존성 주입, 테스트까지 — 지금까지 다진 도구를 한곳에 모읍니다.

4부의 구성:

만드는 것은 작은 할 일 API — 사용자 회원가입 / 로그인, 할 일 CRUD, 비동기 작업 처리, 테스트, 배포까지. 매 챕터는 그 위에 한 단계씩 쌓는 구성입니다.

왜 FastAPI 인가 #

Python 웹 프레임워크는 여럿입니다. Django, Flask, FastAPI, Litestar, Starlette 등. 각자 강점이 있습니다.

DjangoFlaskFastAPI
스타일풀스택 (배터리 포함)마이크로마이크로 + 타입
비동기부분 (4.0+)외부 라이브러리네이티브
타입 힌트 활용보조적보조적핵심 동작
자동 문서 (OpenAPI)별도 라이브러리별도빌트인
학습 곡선가파름매우 평탄평탄
데이터 검증DRF + 직접직접Pydantic 자동

FastAPI의 강점은 타입 힌트가 그대로 동작 한다는 점입니다. 함수 시그니처를 적으면 그게 곧:

  • 입력 검증
  • 직렬화 / 역직렬화
  • OpenAPI 스키마 (Swagger UI 자동 생성)
  • 에디터 자동완성
  • 정적 타입 검사

이 다섯이 한 번에 일어납니다. 다른 프레임워크는 각각을 별도 설정으로 잡아야 합니다. 본 책에서 다진 타입 힌트가 가장 잘 통하는 영역이 FastAPI이고, 그래서 책의 수렴점으로 잡았습니다.

프로젝트 셋업 #

1장의 uv 흐름으로 시작합니다.

프로젝트 만들기
uv init todo-api --python 3.14
cd todo-api

의존성 설치 #

실행에 필요한 의존성
uv add "fastapi[standard]"

fastapi[standard] 한 줄에 따라오는 것들:

  • FastAPI — 프레임워크 본체
  • uvicorn — ASGI 서버 (개발 / 프로덕션)
  • pydantic — 데이터 검증
  • httpx — 테스트용 클라이언트 (28장에서)
  • email-validatorEmailStr 검증
  • python-multipart — 폼 / 파일 업로드
  • fastapi-clifastapi CLI 명령

다음으로 개발용 도구.

개발 도구
uv add --dev pytest pytest-asyncio ruff pyright

30장 타입체커 설정과 CI 통합에서 본 도구들의 정식 셋업을 다룹니다. FastAPI 테스트의 표준은 pytest + httpx 조합입니다 (28장 테스트와 배포에서 자세히).

첫 엔드포인트 #

app/main.py
from fastapi import FastAPI

app = FastAPI(
    title="Todo API",
    version="0.1.0",
    description="모던 파이썬 책의 예제 API",
)

@app.get("/")
def read_root() -> dict[str, str]:
    return {"hello": "fastapi"}

이게 끝입니다. 7 줄의 파이썬으로 HTTP 서버가 됩니다.

실행 #

개발 모드
uv run fastapi dev app/main.py

fastapi dev가 하는 일:

  • uvicorn을 띄우고
  • 코드 변경 시 자동 리로드
  • 디버그 로그
  • LAN 노출 안 함 (127.0.0.1만)

브라우저에서 http://127.0.0.1:8000을 열면:

{"hello": "fastapi"}

더 중요한 — 자동 문서 #

같은 서버의 /docs로 가보세요. Swagger UI가 자동으로 떠 있습니다. 엔드포인트 목록, 입력 스키마, 응답 모델, 바로 호출해 볼 수 있는 “Try it out” 버튼까지. 코드 한 줄도 더 안 적었습니다.

/redoc으로 가면 ReDoc 스타일 문서도 같이 있습니다.

이게 FastAPI가 말하는 자동 문서의 정체입니다. 함수 시그니처 + 타입 힌트 + docstring이 그대로 OpenAPI 명세로 변환됩니다.

더 풍부한 엔드포인트 #

여러 메소드 / 경로
from fastapi import FastAPI

app = FastAPI(title="Todo API", version="0.1.0")

@app.get("/")
def read_root() -> dict[str, str]:
    """루트 — 헬스체크용."""
    return {"hello": "fastapi"}

@app.get("/items/{item_id}")
def read_item(item_id: int) -> dict[str, int]:
    """ID 로 단일 아이템 조회."""
    return {"item_id": item_id}

@app.get("/items")
def list_items(skip: int = 0, limit: int = 10) -> dict[str, int]:
    """페이지네이션 쿼리 파라미터."""
    return {"skip": skip, "limit": limit}

여기서 일어나는 자동 변환:

  • 경로 매개변수 {item_id} → 함수 인자 item_id: int
    • int가 아니면 422 Unprocessable Entity 자동 응답
  • 쿼리 매개변수 skip, limit → 함수의 기본값 있는 인자
    • ?skip=10&limit=20 형태로 자동 파싱
  • 반환 타입 -> dict[str, int] → JSON 응답
  • docstring → Swagger UI의 설명

int 타입을 적었는데 자동으로 변환 · 검증이 되는 게 핵심입니다. Flask 라면:

🚫 Flask와 비교
@app.route("/items/<int:item_id>")
def read_item(item_id):
    return jsonify({"item_id": item_id})

<int:item_id> 같이 라우터에 타입을 적고, 검증 실패 응답을 직접 처리해야 합니다. FastAPI는 함수 시그니처가 라우터 명세입니다.

Path, Query — 더 세밀한 검증 #

고급 검증
from fastapi import FastAPI, Path, Query
from typing import Annotated

app = FastAPI()

@app.get("/items/{item_id}")
def read_item(
    item_id: Annotated[int, Path(ge=1, le=10000, description="아이템 ID")],
    q: Annotated[str | None, Query(min_length=3, max_length=50)] = None,
):
    return {"item_id": item_id, "q": q}

Path(), Query()검증 규칙메타데이터를 추가합니다.

  • ge=1>= 1
  • le=10000<= 10000
  • min_length=3 — 최소 3자
  • description=... — Swagger 문서에 표시

Annotated[T, ...] 패턴이 20장 typing 고급에서 본 그것입니다 — 타입에 메타데이터를 묶는 표준. FastAPI의 거의 모든 의존성 주입이 본 패턴 위에 만들어졌습니다.

응답 — 자동 직렬화 #

dataclass / Pydantic 모델 반환
from datetime import datetime, timezone
from pydantic import BaseModel

class Todo(BaseModel):
    id: int
    title: str
    done: bool
    created_at: datetime

@app.get("/todos/1")
def get_todo() -> Todo:
    return Todo(
        id=1,
        title="FastAPI 학습",
        done=False,
        created_at=datetime.now(timezone.utc),
    )

응답:

{
  "id": 1,
  "title": "FastAPI 학습",
  "done": false,
  "created_at": "2026-05-01T12:00:00+00:00"
}

datetime 같은 비-JSON 타입도 자동 변환 됩니다. Pydantic이 직렬화를 담당합니다. 이걸 직접 짜면 라우트마다 변환 코드를 써야 하지만, FastAPI는 반환 타입만 적으면 끝.

Todo 모델에 대한 자세한 내용은 23장 라우팅, Pydantic 모델, 의존성 주입과 24장 Pydantic v2 깊이에서.

에러 — HTTPException #

에러 응답
from fastapi import HTTPException

todos: dict[int, Todo] = {1: Todo(...)}

@app.get("/todos/{todo_id}")
def get_todo(todo_id: int) -> Todo:
    if todo_id not in todos:
        raise HTTPException(status_code=404, detail="Todo not found")
    return todos[todo_id]

raise HTTPException(...) 한 줄로 JSON 에러 응답이 나갑니다.

{"detail": "Todo not found"}

표준 HTTP 상태 코드 (404, 400, 500 등)와 함께. 4부 진행하면서 사용자 정의 예외 핸들러도 다룹니다.

프로젝트 구조 — 처음부터 단정하게 #

작은 API라도 처음부터 모듈 / 패키지로 나누면 7장 모듈, 패키지와 pyproject.toml에서 본 흐름이 그대로 살아납니다.

기본 구조 (4부가 채울 모양)
todo-api/
├── pyproject.toml
├── app/
│   ├── __init__.py
│   ├── main.py            # FastAPI 인스턴스, 라우터 등록
│   ├── core/
│   │   ├── config.py      # 환경 변수
│   │   └── security.py    # JWT, 패스워드 (26장)
│   ├── api/
│   │   ├── __init__.py
│   │   ├── deps.py        # 의존성 주입 (23, 26장)
│   │   ├── todos.py       # 할 일 라우터
│   │   └── auth.py        # 인증 라우터 (26장)
│   ├── models/            # SQLAlchemy 모델 (25장)
│   │   └── todo.py
│   ├── schemas/           # Pydantic 스키마 (23, 24장)
│   │   └── todo.py
│   └── db/                # DB 연결 (25장)
│       └── session.py
├── alembic/               # 마이그레이션 (25장)
└── tests/                 # pytest (28장)
    └── test_todos.py

본 챕터에서는 app/main.py 한 파일이지만, 다음 챕터부터 한 디렉터리씩 채워 나갑니다. 29장 종합 실습 — TODO API 완성하기에서 모든 디렉터리가 채워진 형태가 됩니다.

환경 변수 — pydantic-settings #

설정값 (DB URL, JWT 시크릿 등)을 코드에 박지 말고 환경 변수로 분리하는 게 표준입니다. FastAPI 진영의 표준 도구는 pydantic-settings.

설치
uv add pydantic-settings
app/core/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    app_name: str = "Todo API"
    database_url: str = "sqlite+aiosqlite:///./todo.db"
    jwt_secret: str = "dev-only-change-me"
    jwt_expire_minutes: int = 60

    model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")

settings = Settings()

.env 파일에서 자동으로 읽고, 환경 변수가 있으면 그걸로 덮어씁니다. 타입 검증까지 포함이라 잘못된 환경 변수는 시작 시점에 막힙니다.

.env
JWT_SECRET=production-secret-here
DATABASE_URL=postgresql+asyncpg://user:pw@localhost/todo

.env는 git에서 제외 (.gitignore에 추가). 보안 주의 — 31장 logging과 관측성에서 secret 로깅 방지도 다룹니다.

빠르게 정리한 첫 챕터의 결과물 #

app/main.py — 본 챕터의 마무리
from fastapi import FastAPI, HTTPException
from app.core.config import settings

app = FastAPI(
    title=settings.app_name,
    version="0.1.0",
)

@app.get("/")
def read_root() -> dict[str, str]:
    return {"app": settings.app_name, "status": "ok"}

@app.get("/health")
def health() -> dict[str, str]:
    return {"status": "healthy"}
실행
uv run fastapi dev app/main.py
# → http://127.0.0.1:8000
# → http://127.0.0.1:8000/docs

엔드포인트 두 개 + 자동 문서 + 환경 변수 분리. 다음 챕터의 출발점입니다.

연습문제 #

  1. uv init todo-api --python 3.14로 프로젝트를 만들고 fastapi[standard]를 추가하세요. app/main.pyGET /, GET /items/{item_id}, GET /items?skip&limit 세 엔드포인트를 작성하고 fastapi dev로 띄운 뒤 /docs에서 Swagger UI가 자동 생성되는지 확인합니다.
  2. Annotated[int, Path(ge=1, le=100)]item_id에 범위 검증을 추가하세요. 범위 밖 ID로 호출하면 자동으로 422 응답이 오는지 확인합니다. Swagger UI의 입력 폼에 검증 규칙이 표시되는지도 확인합니다.
  3. pydantic-settingsapp/core/config.py를 작성하고 .env에서 app_name / database_url을 읽도록 합니다. 환경 변수가 잘못된 타입 (예: database_url에 빈 문자열) 일 때 시작 시점에 에러가 나는지 확인합니다.

한 줄 요약: FastAPI의 강점은 타입 힌트가 검증 / 직렬화 / 문서 / 자동완성에 그대로 동작. uv add "fastapi[standard]" 한 줄로 uvicorn / pydantic / httpx 다 따라옴. fastapi dev로 자동 리로드. 함수 시그니처가 라우터 명세, /docs에 Swagger 자동. Annotated[T, Path(...)] / Annotated[T, Query(...)]로 검증 강화, Pydantic 모델 반환 시 datetime 등 자동 직렬화, HTTPException으로 에러, pydantic-settings로 환경 변수 분리, 처음부터 모듈 / 패키지 구조.

다음 챕터 #

다음 23장 라우팅, Pydantic 모델, 의존성 주입에서는 APIRouter로 라우트를 분리하고, Pydantic 스키마를 본격적으로 사용하고, 의존성 주입으로 라우트 간 공통 로직 (인증된 사용자, DB 세션 등)을 분리하는 패턴을 다룹니다.

X