FastAPI 시작과 셋업
왜 FastAPI인가, uv로 첫 프로젝트 셋업, Hello FastAPI, OpenAPI/Swagger UI 자동 생성까지 한곳에 정리합니다.
4부 실전 FastAPI의 시작입니다. 1부 (입문) → 2부 (구조화) → 3부 (깊이 · 동시성)의 21장이 다 들어갈 무대입니다. FastAPI로 한 프로젝트를 만들면서 타입 힌트, dataclass / Pydantic, async / await, 의존성 주입, 테스트까지 — 지금까지 다진 도구를 한곳에 모읍니다.
4부의 구성:
- 22장 시작과 셋업, OpenAPI ← 본 챕터
- 23장 라우팅, Pydantic 모델, 의존성 주입
- 24장 Pydantic v2 깊이 ★신규
- 25장 DB 연동 — SQLAlchemy 2.x + Alembic
- 26장 인증 — OAuth2 패스워드 플로우 + JWT
- 27장 비동기와 백그라운드 작업
- 28장 테스트와 배포 — pytest, Docker, Railway/Fly
- 29장 종합 실습 — TODO API 완성하기 ★신규
만드는 것은 작은 할 일 API — 사용자 회원가입 / 로그인, 할 일 CRUD, 비동기 작업 처리, 테스트, 배포까지. 매 챕터는 그 위에 한 단계씩 쌓는 구성입니다.
왜 FastAPI 인가 #
Python 웹 프레임워크는 여럿입니다. Django, Flask, FastAPI, Litestar, Starlette 등. 각자 강점이 있습니다.
| Django | Flask | FastAPI | |
|---|---|---|---|
| 스타일 | 풀스택 (배터리 포함) | 마이크로 | 마이크로 + 타입 |
| 비동기 | 부분 (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-validator —
EmailStr검증 - python-multipart — 폼 / 파일 업로드
- fastapi-cli —
fastapiCLI 명령
다음으로 개발용 도구.
uv add --dev pytest pytest-asyncio ruff pyright30장 타입체커 설정과 CI 통합에서 본 도구들의 정식 셋업을 다룹니다. FastAPI 테스트의 표준은 pytest + httpx 조합입니다 (28장 테스트와 배포에서 자세히).
첫 엔드포인트 #
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.pyfastapi 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: intint가 아니면 422 Unprocessable Entity 자동 응답
- 쿼리 매개변수
skip,limit→ 함수의 기본값 있는 인자?skip=10&limit=20형태로 자동 파싱
- 반환 타입
-> dict[str, int]→ JSON 응답 - docstring → Swagger UI의 설명
int 타입을 적었는데 자동으로 변환 · 검증이 되는 게 핵심입니다. 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—>= 1le=10000—<= 10000min_length=3— 최소 3자description=...— Swagger 문서에 표시
Annotated[T, ...] 패턴이 20장 typing 고급에서 본 그것입니다 — 타입에 메타데이터를 묶는 표준. FastAPI의 거의 모든 의존성 주입이 본 패턴 위에 만들어졌습니다.
응답 — 자동 직렬화 #
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에서 본 흐름이 그대로 살아납니다.
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-settingsfrom 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 파일에서 자동으로 읽고, 환경 변수가 있으면 그걸로 덮어씁니다. 타입 검증까지 포함이라 잘못된 환경 변수는 시작 시점에 막힙니다.
JWT_SECRET=production-secret-here
DATABASE_URL=postgresql+asyncpg://user:pw@localhost/todo.env는 git에서 제외 (.gitignore에 추가). 보안 주의 — 31장 logging과 관측성에서 secret 로깅 방지도 다룹니다.
빠르게 정리한 첫 챕터의 결과물 #
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엔드포인트 두 개 + 자동 문서 + 환경 변수 분리. 다음 챕터의 출발점입니다.
연습문제 #
uv init todo-api --python 3.14로 프로젝트를 만들고fastapi[standard]를 추가하세요.app/main.py에GET /,GET /items/{item_id},GET /items?skip&limit세 엔드포인트를 작성하고fastapi dev로 띄운 뒤/docs에서 Swagger UI가 자동 생성되는지 확인합니다.Annotated[int, Path(ge=1, le=100)]로item_id에 범위 검증을 추가하세요. 범위 밖 ID로 호출하면 자동으로 422 응답이 오는지 확인합니다. Swagger UI의 입력 폼에 검증 규칙이 표시되는지도 확인합니다.pydantic-settings로app/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 세션 등)을 분리하는 패턴을 다룹니다.