이번 편은 FastAPI 시리즈 전반부의 정확한 중간 지점(12/20) 입니다. 지금까지 배운 의존성, SQLModel, 인증, 환경 설정을 하나의 학습용 작업 관리 API로 묶어 보면서 빈틈을 점검합니다. 이 미니 서비스는 앞으로 20편 캡스톤으로 가기 전에 숨을 고르며 "이 정도 구성은 반복해서 만들 수 있다"는 감각을 다지는 단계입니다.
준비: 우리가 완성할 첫 서비스
/auth/token으로 로그인(도장 받기)/tasks에 할 일 추가하기- 새로고침해도 데이터가 남아 있는지 확인하기
이 짧은 플로우가 이미 "설정 + DB + 인증"을 모두 거친 결과물입니다. 아래 섹션에서 이 세 단계가 어디에서 조립되는지 확인합니다.
이번 글에서 새로 나오는 용어
- 미니 서비스: 한 팀이 독립적으로 만들고 배포할 수 있는 작지만 완결된 API 묶음으로, 인증·DB·설정이 모두 들어 있는 이번 실습의 목표입니다.
- core/config: pydantic-settings로 환경 값을 모아 두는 영역으로, 토큰 만료 시간·DB URL 같은 공통 설정을 한곳에서 관리합니다.
- api/deps: FastAPI
Depends에 연결할 공통 함수들을 모아 둔 모듈로, 세션·현재 사용자 같은 값이 모든 라우터에 주입됩니다. - lifespan 이벤트: FastAPI 앱이 시작·종료될 때 실행되는 훅으로, 여기서
init_db()를 호출해 첫 요청 전에 테이블을 생성합니다. - 토큰 보호 라우터:
get_current_user의존성을 통해 JWT를 검증한 뒤에만 접근을 허용하는 엔드포인트로,/tasksCRUD를 안전하게 감싸는 방법입니다.
실습 카드
- 예상 소요 시간: 75분
- 사전 준비: 9~11편 예제 코드, Docker/uvicorn 실행 경험
- 실습 목표: 설정·DB·인증 라우터를 통합해 학습용 Todo 서비스를 완성한다
폴더 구조 재점검
app/
├─ core/
│ ├─ config.py
│ └─ security.py
├─ db/
│ ├─ models.py
│ ├─ session.py
│ └─ init.py
├─ api/
│ ├─ deps.py
│ ├─ routes/
│ │ ├─ auth.py
│ │ └─ tasks.py
│ └─ __init__.py
└─ main.py
폴더는 관심사를 기준으로 나눕니다. 이렇게 나눠야 테스트에서도 필요한 모듈만 바꿔 끼울 수 있습니다.
이 다이어그램 하나로 구성 요소들이 어디서 만나는지 설명할 수 있어야 합니다. 팀 동료에게 구조를 설명할 때도 이 순서를 그대로 따라가며 문서화해 보세요. 막히면 맨 앞에서 정리한 3단계 흐름을 다시 떠올리고, 각 단계가 어느 파일에 연결되는지 짝지어 봅니다.
설정과 보안 모듈
core/config.py:
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
secret_key: str
access_token_expire_minutes: int = 30
database_url: str = "sqlite:///./todo.db"
model_config = {"env_file": ".env", "env_prefix": "APP_"}
settings = Settings()
core/security.py에는 비밀번호 해시와 JWT 생성 로직을 둡니다. 보안을 한 파일에 모으면 테스트와 점검이 쉬워집니다.
DB 세션과 모델
db/session.py에서 엔진과 Session 의존성을 정의합니다. db/models.py에서는 User, Task 모델을 선언합니다.
class User(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
email: str = Field(index=True, unique=True)
hashed_password: str
class Task(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
title: str
done: bool = False
owner_id: int = Field(foreign_key="user.id")
api/deps.py에는 get_session, get_current_user 등 공통 의존성을 모읍니다. 같은 함수를 모든 라우터에서 재사용해 코드 중복을 줄입니다.
라우터 구성
api/routes/auth.py는 10편에서 다룬 토큰 발급 흐름을 담당합니다. api/routes/tasks.py는 사용자별 CRUD를 제공합니다.
tasks_router = APIRouter(prefix="/tasks", tags=["tasks"])
@tasks_router.get("", response_model=list[TaskRead])
def list_tasks(
current_user: User = Depends(get_current_user),
session: Session = Depends(get_session),
):
statement = select(Task).where(Task.owner_id == current_user.id)
return session.exec(statement).all()
@tasks_router.post("", response_model=TaskRead, status_code=201)
def create_task(
payload: TaskCreate,
current_user: User = Depends(get_current_user),
session: Session = Depends(get_session),
):
record = Task(**payload.model_dump(), owner_id=current_user.id)
session.add(record)
session.commit()
session.refresh(record)
return record
app/main.py에서는 라우터를 묶고 lifespan 이벤트로 init_db()를 호출합니다. 앱 시작 때 DB가 준비돼야 첫 요청이 실패하지 않습니다. 학생 입장에서는 "서버 켜질 때 책상 정리하기" 정도로 이해하면 됩니다.
@asynccontextmanager
async def lifespan(app: FastAPI):
init_db()
yield
app = FastAPI(title="Todo API", lifespan=lifespan)
app.include_router(auth_router)
app.include_router(tasks_router)
시나리오 기반 테스트
/auth/register로 사용자 생성 혹은 초기 사용자 스크립트 실행./auth/token에서 토큰 발급.Authorization헤더를 붙여/tasksCRUD 호출..env에는APP_DATABASE_URL=sqlite:///./todo.db를 두고, 배포 환경에서는 PostgreSQL URL을 주입.
이 시나리오는 Pytest TestClient와 dependency_overrides로 자동화할 수 있습니다. 인증 과정을 단축한 더미 의존성으로 테스트 속도도 잡습니다.
기대 출력 확인
중간 점검 서비스는 아래 흐름이 재현되면 합격입니다.
:::terminal{title="미니 서비스 검증 흐름", showFinalPrompt="false"}
[
{ "cmd": "http POST :8000/auth/token [email protected] password=secret", "output": "HTTP/1.1 200 OK\n{\n \"access_token\": \"eyJhbGciOi...\",\n \"token_type\": \"bearer\"\n}", "delay": 500 },
{ "cmd": "http GET :8000/tasks", "output": "HTTP/1.1 401 Unauthorized\n{\n \"detail\": \"Not authenticated\"\n}", "delay": 400 },
{ "cmd": "http GET :8000/tasks \"Authorization:Bearer eyJhbGciOi...\"", "output": "HTTP/1.1 200 OK\n[]", "delay": 400 },
{ "cmd": "http POST :8000/tasks title='API 정리' \"Authorization:Bearer eyJhbGciOi...\"", "output": "HTTP/1.1 201 Created\n{\n \"id\": 1,\n \"title\": \"API 정리\",\n \"done\": false\n}", "delay": 400 }
]
:::
- 확인할 점: 토큰 없이는 401이 나오는지
- 확인할 점: 같은 경로가 토큰을 넣으면 200으로 바뀌는지
- 확인할 점: 생성 결과가 JSON으로 명확히 돌아오는지
(선택) 배포 체크리스트
- Docker 이미지 빌드 → 컨테이너 레지스트리에 푸시.
- DB 마이그레이션 스크립트 실행.
uvicorn을 systemd 서비스 혹은 컨테이너 오케스트레이션으로 실행.- 로그와 헬스체크 엔드포인트를 모니터링 도구에 연결.
실습
- 따라 하기: 구조 예시대로 폴더를 만들고 설정/DB/라우터 모듈을 연결해
/tasksCRUD가 토큰 인증 뒤에만 열리게 한다. - 확장하기: Pytest 시나리오를 작성해 회원가입 → 토큰 발급 → 작업 생성 흐름을 자동 검증한다.
- 디버깅:
.env값을 바꿨을 때 DB 연결이 실패하면 로그를 확인해 원인을 기록한다. - 완료 기준: 하나의 커맨드로 서버가 뜨고, 토큰 없이 요청 시 401·토큰 포함 시 200이 재현된다.
(선택) 다음 확장 아이디어
- WebSocket 엔드포인트로 실시간 작업 업데이트 제공.
- Celery, RQ 같은 작업 큐와 연계해 장기 실행 작업 처리.
- OpenAPI 문서에서 OAuth2 스코프를 정의해 권한별 토큰을 발급.
- 프런트엔드(React/Vue/Svelte)와 연동해 실제 사용자 인터페이스 제공.
마무리
이번 편은 시리즈 전반부의 통합 체크포인트일 뿐입니다. 설정·인증·DB 흐름을 한 번에 다시 짚어 본 만큼, 이제 13~19편에서 파일, 스트리밍, 테스트, 관측 가능성 같은 확장 기능을 차근히 쌓을 차례입니다. 이 토대를 단단히 다지면 20편 캡스톤에서 진짜 완성본을 조립할 때 훨씬 수월해집니다.
💬 댓글
이 글에 대한 의견을 남겨주세요