오늘은 EasyPrompt 프로젝트의 MVP를 처음부터 구축하고, 실제 프로덕션 환경에 배포하기까지 겪은 과정을 정리합니다.
FastAPI 기반 백엔드와 Vite 기반 프론트엔드로 기본 골격을 잡았습니다.
백엔드 구조
models/ : User, Prompt, Category, Favorite, History, PromptForm 등 도메인 모델routers/ : auth, prompts, categories, favorites, generate, prompt_forms, usersschemas/ : Pydantic 스키마 분리utils/security.py : 인증/보안 유틸seed.py : 카테고리, 프롬프트, 관리자 계정 초기 시드 데이터프론트엔드
처음부터 라우터/스키마/모델을 계층별로 명확히 분리해서 이후 기능 추가가 수월하도록 구조를 잡은 게 핵심이었습니다.
프론트엔드가 로컬 개발 중에는 http://localhost:8000을 바라보지만, 배포 후에는 실제 API 서버(https://easyprompt-api.onrender.com)를 바라봐야 했습니다.
// frontend/src/api/client.js
const client = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000',
...
})
환경변수로 분리하고, .env.example과 .env.local을 추가했습니다. VITE_API_BASE_URL은 비밀값이 아니라 빌드에 그대로 포함되는 공개 URL이라 .gitignore 예외 처리로 커밋에 포함시켰습니다.
배포 후 프론트엔드(https://easyprompt.pages.dev)에서 API 호출 시 CORS 에러가 발생했습니다. 원인은 백엔드 CORS 허용 origin이 로컬 개발 서버(http://localhost:5173)만 등록되어 있었기 때문입니다.
# backend/app/main.py
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173", "https://easyprompt.pages.dev"],
...
)
프로덕션 프론트엔드 도메인을 origin 목록에 추가해서 해결했습니다.
프로덕션 DB에 테이블이 없는 상태로 배포되는 문제가 있어서, 앱 시작 시점(lifespan)에 테이블 생성과 시드 데이터 삽입을 자동화했습니다.
@asynccontextmanager
async def lifespan(app: FastAPI):
Base.metadata.create_all(bind=engine)
db = SessionLocal()
try:
seed_data(db)
finally:
db.close()
yield
app = FastAPI(title="EasyPrompt API", lifespan=lifespan)
기존 seed() 함수에서 시드 로직만 seed_data(db)로 분리해서, CLI로 직접 실행할 때와 앱 시작 시 자동 실행할 때 모두 재사용할 수 있게 만들었습니다. 이미 데이터가 있으면 건너뛰도록 되어 있어 재배포 시에도 안전합니다.