
구조
fastapi_mini_project/
├── app/
│ ├── api/ # FastAPI 라우터 (엔드포인트)
│ ├── core/ # 공통 설정, 보안, 의존성 (Config, JWT, Password 등)
│ ├── models/ # Tortoise ORM 모델 (DB 테이블 정의)
│ ├── schemas/ # Pydantic 스키마 (요청/응답 데이터 검증)
│ ├── services/ # 비즈니스 로직 (회원가입, 로그인, CRUD 처리)
│ ├── repositories/ # DB 접근 로직 (CRUD 메서드)
│ ├── db/ # DB 연결 및 마이그레이션 설정
│ ├── scraping/ # 스크래핑 기능 (명언/질문 수집용)
│ ├── main.py # FastAPI 진입점 (앱 실행 및 라우터 등록)
│ └── __init__.py
├── tests/ # 테스트 코드
├── .env / .env.dev # 환경변수
├── pyproject.toml # Poetry 설정
└── README.md # 프로젝트 설명
| 폴더 | 주요 역할 | 예시 파일 |
|---|
| api/ | 실제로 클라이언트 요청이 들어오는 엔드포인트 정의 | auth.py, diary.py |
| core/ | 앱의 핵심 설정 (DB URL, JWT, 암호화 등) | config.py, security.py, dependencies.py |
| models/ | 데이터베이스 테이블 정의 (Tortoise ORM 모델) | user.py, diary.py |
| schemas/ | 요청(Request) / 응답(Response) 검증용 모델 (Pydantic) | user.py |
| services/ | 핵심 비즈니스 로직 (회원가입, 로그인 등) | auth_service.py |
| repositories/ | DB 조작 담당 (CRUD 기능 구현) | user_repo.py |
| db/ | DB 연결 및 마이그레이션 관련 | base.py, session.py |
| scraping/ | 외부 웹 데이터 수집 기능 | quote_scraper.py |
| tests/ | pytest 등으로 테스트 실행 | test_auth.py |
| main.py | FastAPI 앱 실행, 라우터 등록, DB 초기화 | |
상세 구조
fastapi_mini_project/
├── app/
│ ├── api/
│ │ └── v1/
│ │ ├── auth.py
│ │ ├── diary.py
│ │ ├── quote.py
│ │ ├── question.py
│ │ └── __init__.py
│ │
│ ├── core/
│ │ ├── config.py
│ │ ├── security.py
│ │ ├── dependencies.py
│ │ └── __init__.py
│ │
│ ├── models/
│ │ ├── user.py
│ │ ├── diary.py
│ │ ├── quote.py
│ │ ├── question.py
│ │ └── __init__.py
│ │
│ ├── schemas/
│ │ ├── user.py
│ │ ├── diary.py
│ │ ├── quote.py
│ │ ├── question.py
│ │ └── __init__.py
│ │
│ ├── services/
│ │ ├── auth_service.py
│ │ ├── diary_service.py
│ │ ├── quote_service.py
│ │ ├── question_service.py
│ │ └── __init__.py
│ │
│ ├── repositories/
│ │ ├── user_repo.py
│ │ ├── diary_repo.py
│ │ ├── quote_repo.py
│ │ ├── question_repo.py
│ │ └── __init__.py
│ │
│ ├── db/
│ │ ├── base.py
│ │ ├── session.py
│ │ └── migrations/
│ │
│ ├── scraping/
│ │ ├── quote_scraper.py
│ │ ├── question_scraper.py
│ │ └── __init__.py
│ │
│ ├── main.py
│ └── __init__.py
│
├── tests/
│ ├── test_auth.py
│ ├── test_diary.py
│ ├── test_quote.py
│ ├── test_question.py
│
├── .env
├── .env.dev
├── pyproject.toml
├── poetry.lock
└── README.md
프로젝트하면서 공부가 된 부분
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")
- "요청의 HTTP 헤더에서 토큰을 자동으로 추출해주는 의존성"
- FastAPI는 이걸 통해 Authorization: Bearer <토큰> 형식의 헤더를 자동으로 감지하고,
- 흐름
- 사용자가 로그인 요청을 보냄 -> /auth/login에서 아이디와 비밀번호를 검증
- JWT 토큰(Access Token)을 발급받음
- 이후 API 요청 시, 사용자는 헤더에 다음처럼 토큰을 포함해서 요청을 보냄
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6...
- oauth2_scheme이 작동해서, FastAPI가 이 헤더를 자동으로 인식하고 토큰만 추출
@router.post("/logout")
async def logout(token: str = Depends(oauth2_scheme)):
return await AuthService.logout(token)
GET /diary
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6...
이렇게 클라이언트가 요청을 보내면 Depends(oauth2_scheme) 덕분에
"eyJhbGciOi..." 부분만 token 인자로 자동으로 들어감
- tokenUrl="/auth/login"의 의미
- “토큰을 발급받는 엔드포인트 경로” 를 명시적으로 알려주는 설정값
- 즉, Swagger 문서(/docs)에서 "Authorize" 버튼을 눌렀을 때
- FastAPI가 어디로 로그인 요청을 보내야 할지 이 값을 보고 알아냄
JWT
@staticmethod
async def login(username: str, password: str):
user = await UserRepository.get_by_username(username)
if not user or not verify_password(password, user.password_hash):
raise HTTPException(status_code=401, detail="Invalid credentials")
access_token = create_access_token(data={"sub": user.username})
return {"access_token": access_token, "token_type": "bearer"}
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
username = payload.get("sub")
"sub"은 “subject”의 약자 = 토큰이 발급된 사용자
즉 로그인할 때 넣어준 {"sub": user.username} 값이 들어있음.
- jwt.decode()를 통해 SECRET_KEY와 ALGORITHM으로 디코딩하면
- 원래 들어있던 데이터(payload)를 볼 수 있음
.env 연결 확인
- 연결정보
- psql -h 000.000.00.000 -U todolist_admin -d todolist
DB_USER=todolist_admin
DB_PASSWORD= (비밀번호를 여기 입력)
DB_HOST=000.000.00.000
DB_PORT=5432
DB_NAME=todolist
DB_SCHEME=asyncpg
SECRET_KEY="mysecret"
ALGORITHM="HS256"