본문 바로가기
주전공/캡스톤디자인과창업프로젝트

여름방학 기록(4): BE 개발 시작 | API Endpoint Listup + FastAPI code

by 희띠띠 2025. 8. 17.

8/17(일) - RESTful API Endpoint 리스트업 및 FastAPI 코드 작성

저번시간 완성했던 API 명세서를 기반으로 BE 개발에 필요한 엔드포인트 정리부터 시작했다.

해당 리스트가 정리되면 이를 토대로 FastAPI용 코드를 작성할 계획이다.

 

RESTful API Endpoint Listup

 


📌 RESTful API Endpoint Listup 정리의 필요성

 

1. 팀원 간의 공통 이해 기반 마련

  • 프론트엔드(FE), 백엔드(BE), 추천 시스템 등 파트별로 서로 약속된 통신 규약이 필요함.
  • API 명세가 없으면 FE에서 어떤 URL로 요청해야 할지, 어떤 응답을 받을지 몰라서 구현 속도가 느려짐.
  • 예: /api/v1/market/products 에서 어떤 파라미터를 받는지, 어떤 JSON 구조로 응답하는지 정리하면 FE에서 가짜 데이터(mock)로 먼저 화면을 개발할 수 있음.

 

2. 일관성 있는 구조 유지

  • RESTful 설계 원칙을 따르려면 리소스 이름, 메서드(GET/POST/PUT/DELETE) 규칙이 일관되어야 함.
  • 팀 내 각자 구현 시 혼동을 막음.
    • 예: GET /users/{id} vs GET /userInfo?id=123 → 통일되지 않으면 나중에 유지보수 악몽 발생.

 

3. 디버깅 및 협업 효율성 증가

  • 개발 중 오류가 생기면, API 리스트 문서를 보고 바로 요청/응답 형식을 확인 가능.
  • Postman, Swagger 같은 API 테스트 도구에서 그대로 활용 가능 → QA 단계에서 시간 단축.

 

4. 백엔드-프론트엔드 연동 속도 향상

  • BE API가 완성되기 전에도 FE는 Mock API로 화면을 구성할 수 있음.
  • 즉, FE와 BE를 병렬로 개발할 수 있어 프로젝트 전체 속도가 빨라짐.
  • 나중에 실제 API 연결만 바꿔주면 됨.

 

5. 유지보수 및 확장성 확보

  • 서비스가 커질수록 API 엔드포인트가 많아지고 복잡해짐.
  • 정리된 리스트가 있으면, 새 기능 추가나 리팩토링 시 기존 API와 충돌을 피할 수 있음.
  • 새로운 팀원이 합류했을 때도 빠르게 이해 가능.

 

6. 발표 및 문서화 시 가독성 강화

  • 졸업 프로젝트 발표나 보고서 제출 시, API Endpoint 리스트가 있으면 시스템 구조를 직관적으로 보여줌.
  • "이 시스템은 어떤 기능을 제공하는지"를 한눈에 알 수 있어 평가자/교수님에게 어필 가능.

 

✅ 요약

RESTful API Endpoint Listup은 단순 문서가 아니라,
👉 팀 전체의 공통 언어이자 설계도 역할을 함.
👉 협업 효율을 높이고, FE/BE 연동 속도를 빠르게 하며, 유지보수에도 필수적임.
👉 최종적으로는 발표/보고서에서도 프로젝트의 체계성과 완성도를 보여주는 중요한 자료가 됨.

 


FastAPI code

# main.py
from typing import List, Optional
from fastapi import FastAPI, HTTPException, Body, Path, Query
from pydantic import BaseModel, Field

# --- Pydantic 모델 정의 ---
# 요청 및 응답 데이터 유효성 검사를 위해 사용됩니다.
# API 명세서에 맞춰 필요한 모델을 정의합니다.

# 회원
class UserCreate(BaseModel):
    id: str
    password: str
    email: str
    nickname: str
    gender: Optional[str] = None
    birthday: Optional[str] = None

class UserProfile(BaseModel):
    id: str
    email: str
    nickname: str
    gender: Optional[str] = None
    birthday: Optional[str] = None

class UserLogin(BaseModel):
    id: str
    password: str

class Token(BaseModel):
    message: str
    token: str
    data: dict

# 추천
class Recommendation(BaseModel):
    title: str
    score: float
    distanceKm: float
    reason: List[str]

class RecommendResponse(BaseModel):
    message: str
    data: dict

# 콘텐츠 (축제, 상품)
class Festival(BaseModel):
    name: str
    description: str
    latitude: float
    longitude: float

class Product(BaseModel):
    name: str
    price: int
    description: str
    image_url: str

# 사용자 상호작용
class Favorite(BaseModel):
    item_id: str

class Review(BaseModel):
    user_id: str
    product_id: str
    rating: int = Field(..., ge=1, le=5)
    comment: str

# --- FastAPI 애플리케이션 초기화 ---
app = FastAPI(
    title="소소행 API",
    description="소도시 여행 추천 및 지역 콘텐츠 제공을 위한 RESTful API",
    version="1.0.0"
)

# 임시 데이터베이스 (실제로는 DB 사용)
db_users = {}
db_festivals = {
    "festival-001": Festival(name="논산딸기축제", description="맛있는 딸기 축제입니다.", latitude=36.1, longitude=127.1),
    "festival-002": Festival(name="보령머드축제", description="머드 체험을 즐길 수 있습니다.", latitude=36.3, longitude=126.5),
}
db_products = {
    "prod-001": Product(name="수제 막걸리", price=12000, description="정읍에서 직접 빚은 막걸리", image_url="http://example.com/img1.jpg"),
    "prod-002": Product(name="전통 한지 부채", price=8000, description="수공예 한지 부채", image_url="http://example.com/img2.jpg"),
}
db_favorites = {}

# --- 회원 관리 엔드포인트 ---
@app.post("/users", tags=["회원 관리"])
async def signup(user: UserCreate):
    """
    새로운 사용자 계정을 생성합니다.
    """
    if user.id in db_users:
        raise HTTPException(status_code=409, detail="이미 존재하는 사용자 ID입니다.")
    db_users[user.id] = user
    return {"message": "회원가입 성공", "data": {"id": user.id}}

@app.post("/auth/login", tags=["회원 관리"])
async def login(user: UserLogin):
    """
    사용자 인증 및 토큰을 발급합니다.
    """
    if user.id not in db_users or db_users[user.id].password != user.password:
        raise HTTPException(status_code=401, detail="아이디 또는 비밀번호가 올바르지 않습니다.")
    # 실제로는 JWT 토큰을 생성하여 반환합니다.
    token = f"dummy_jwt_token_for_{user.id}"
    return {"message": "로그인 성공", "token": token, "data": {"id": user.id}}

@app.get("/users/{user_id}", tags=["회원 관리"])
async def get_user_profile(user_id: str = Path(..., title="사용자 ID")):
    """
    특정 사용자의 프로필 정보를 조회합니다.
    """
    if user_id not in db_users:
        raise HTTPException(status_code=404, detail="사용자를 찾을 수 없습니다.")
    user_data = db_users[user_id]
    return user_data

# --- 추천 및 콘텐츠 조회 엔드포인트 ---
@app.get("/recommend", tags=["추천 및 콘텐츠 조회"])
async def get_recommendations(
    lat: float = Query(..., ge=-90, le=90, description="위도"),
    lng: float = Query(..., ge=-180, le=180, description="경도"),
    themes: Optional[str] = Query(None, description="테마 목록 (쉼표로 구분, 예: family,pet,nature)"),
    dateFrom: Optional[str] = Query(None, description="시작 날짜 (YYYY-MM-DD)"),
    dateTo: Optional[str] = Query(None, description="종료 날짜 (YYYY-MM-DD)"),
    radiusKm: int = Query(50, ge=1, le=100, description="반경 (km)"),
    limit: int = Query(20, ge=1, le=50, description="결과 수 제한"),
    minPopularity: Optional[float] = Query(None, ge=0, description="최소 인기 점수")
):
    """
    사용자 위치, 테마, 날짜, 거리 등 필터를 기반으로 콘텐츠를 추천합니다.
    """
    # 실제 추천 로직 (캐시, TourAPI 연동, 스코어링 등)이 여기에 구현됩니다.
    # 이 예시에서는 더미 데이터를 반환합니다.
    return {
        "message": "추천 리스트 조회 성공",
        "data": {
            "items": [
                {
                    "title": "추천 아이템 1",
                    "score": 0.9,
                    "distanceKm": 5.2,
                    "reason": ["가족 친화적", "자연"]
                },
                {
                    "title": "추천 아이템 2",
                    "score": 0.8,
                    "distanceKm": 10.1,
                    "reason": ["자연경관"]
                }
            ]
        }
    }

@app.get("/festivals", tags=["추천 및 콘텐츠 조회"])
async def get_festivals(
    lat: Optional[float] = Query(None, description="위도"),
    lng: Optional[float] = Query(None, description="경도")
):
    """
    전체 축제 목록을 조회하며, 위치 기반 필터를 적용할 수 있습니다.
    """
    # 이 예시에서는 모든 축제를 반환합니다.
    return list(db_festivals.values())

@app.get("/festivals/{festival_id}", tags=["추천 및 콘텐츠 조회"])
async def get_festival_detail(festival_id: str = Path(..., title="축제 ID")):
    """
    특정 축제의 상세 정보를 반환합니다.
    """
    if festival_id not in db_festivals:
        raise HTTPException(status_code=404, detail="해당 축제를 찾을 수 없습니다.")
    return db_festivals[festival_id]

@app.get("/products", tags=["추천 및 콘텐츠 조회"])
async def get_products():
    """
    마켓에 등록된 전체 상품 목록을 조회합니다.
    """
    return list(db_products.values())

@app.get("/products/{product_id}", tags=["추천 및 콘텐츠 조회"])
async def get_product_detail(product_id: str = Path(..., title="상품 ID")):
    """
    특정 상품의 상세 정보를 반환합니다.
    """
    if product_id not in db_products:
        raise HTTPException(status_code=404, detail="해당 상품을 찾을 수 없습니다.")
    return db_products[product_id]

# --- 사용자 상호작용 엔드포인트 ---
@app.get("/users/{user_id}/favorites", tags=["사용자 상호작용"])
async def get_favorites(user_id: str = Path(..., title="사용자 ID")):
    """
    사용자가 찜한 모든 항목(상품, 여행지 등)을 조회합니다.
    """
    return db_favorites.get(user_id, [])

@app.post("/users/{user_id}/favorites/{item_id}", tags=["사용자 상호작용"])
async def add_favorite(
    user_id: str = Path(..., title="사용자 ID"),
    item_id: str = Path(..., title="찜할 아이템 ID")
):
    """
    찜 목록에 특정 아이템을 추가합니다.
    """
    if user_id not in db_favorites:
        db_favorites[user_id] = []
    if item_id not in db_favorites[user_id]:
        db_favorites[user_id].append(item_id)
    return {"message": "찜 목록 추가 성공"}

@app.delete("/users/{user_id}/favorites/{item_id}", tags=["사용자 상호작용"])
async def remove_favorite(
    user_id: str = Path(..., title="사용자 ID"),
    item_id: str = Path(..., title="삭제할 아이템 ID")
):
    """
    찜 목록에서 특정 아이템을 삭제합니다.
    """
    if user_id in db_favorites and item_id in db_favorites[user_id]:
        db_favorites[user_id].remove(item_id)
    return {"message": "찜 목록 삭제 성공"}

 


*깃허브 레포 구조

backend/
  app/                        # FastAPI 앱 소스코드
    main.py                   # FastAPI 진입점 (app 객체 생성, 라우터 등록)
    router/                   # API 엔드포인트 라우터 모음
      user_router.py          # 사용자 회원가입/로그인/북마크 등 사용자 관련 API
      recommend_router.py     # 추천 시스템 관련 API (여행지/상품 추천)
      festival_router.py      # 축제/행사 정보 관련 API (TourAPI 연동)
      market_router.py        # 로컬 특산물 마켓 관련 API (상품, 가게)
      common_router.py        # 공통 엔드포인트 (health check 등)
    services/                 # 비즈니스 로직, DB 트랜잭션 처리
      user_service.py         # 사용자 CRUD 및 인증/보안 처리
      recommend_service.py    # 추천 모델 호출, 추천 결과 생성 로직
      festival_service.py     # 축제 데이터 처리, TourAPI 연동 로직
      market_service.py       # 마켓/상품 CRUD, 검색/필터 처리
      common_service.py       # DB 세션 의존성, 공통 유틸 서비스
    models/                   # SQLAlchemy 모델 및 Pydantic 스키마
      user_models.py          # 사용자 관련 테이블/스키마
      recommend_models.py     # 추천 시스템에 필요한 모델/스키마
      festival_models.py      # 축제/행사 관련 테이블/스키마
      market_models.py        # 마켓/상품/카테고리 관련 테이블/스키마
      common_models.py        # Base 클래스, TimestampMixin 등 공통 모델
    core/                     # 핵심 설정 및 인프라 레벨 모듈
      config.py               # 환경변수 설정 (pydantic-settings 등)
      cache.py                # Redis 캐시/세션 관리
      database.py             # DB 연결, 세션 로직
    __init__.py               # 패키지 초기화 파일
  tests/                      # 단위/통합 테스트 모음 (pytest)
    recommend_test.py         # 추천 API/서비스 테스트
    festival_test.py          # 축제 API/서비스 테스트
    market_test.py            # 마켓 API/서비스 테스트
    tourapi_test.py           # TourAPI 연동 테스트
  data/                       # 데이터셋 보관 (raw/processed)
    raw/                      # 원본 데이터 (크롤링/다운로드)
    processed/                # 전처리된 데이터 (모델 학습용)
  RecommendModels/            # 추천 시스템 모델/인덱스 파일
    feature_store.parquet     # 사용자/아이템 feature 저장소
    nn_index.joblib           # 최근접 이웃 검색용 인덱스
  pyproject.toml              # Poetry 설정 (패키지/빌드 관리)
  requirements.txt            # pip 기반 패키지 의존성 리스트
  README.md                   # 프로젝트 개요 및 실행 방법 문서