FastAPI + SvelteKit, 왜 이 조합인가?

최근 1인 개발자나 소규모 팀에서 빠르게 서비스를 출시하려는 니즈가 늘면서, 개발 속도와 성능을 동시에 잡을 수 있는 기술 스택에 대한 관심이 높아지고 있습니다. 그중에서도 FastAPI + SvelteKit 조합은 Python의 생산성과 JavaScript의 반응성을 결합한 현실적인 풀스택 선택지로 주목받고 있습니다.
FastAPI는 Python 기반의 고성능 웹 프레임워크로, 자동 문서화(Swagger UI), 타입 힌팅 기반 유효성 검사, 비동기 처리를 기본으로 지원합니다. SvelteKit은 Svelte를 기반으로 한 풀스택 메타프레임워크로, SSR(서버사이드 렌더링), 파일 기반 라우팅, 빌드 최적화를 제공합니다. 이 두 기술을 조합하면 백엔드 API 서버와 프론트엔드 앱을 각각 최적화된 환경에서 운영할 수 있습니다.
1단계: FastAPI 백엔드 프로젝트 세팅
프로젝트 구조
FastAPI 프로젝트는 기능별로 라우터를 분리하고, Pydantic 모델을 통해 요청/응답 스키마를 명확히 정의하는 것이 좋습니다. 아래는 실전에서 사용하는 기본 구조입니다.
backend/
├── app/
│ ├── main.py # FastAPI 앱 진입점
│ ├── routers/
│ │ ├── users.py # 유저 관련 API
│ │ └── items.py # 아이템 관련 API
│ ├── models/
│ │ └── schemas.py # Pydantic 스키마
│ ├── db/
│ │ ├── database.py # DB 연결
│ │ └── models.py # SQLAlchemy 모델
│ └── core/
│ └── config.py # 환경변수 관리
├── requirements.txt
└── .env
main.py 기본 설정
FastAPI 앱의 진입점에서는 CORS 설정, 라우터 등록, 환경변수 로드를 처리합니다. 특히 SvelteKit 프론트엔드와 연동할 때 CORS 설정이 핵심입니다.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.routers import users, items
from app.core.config import settings
app = FastAPI(
title="My Service API",
version="1.0.0",
docs_url="/api/docs"
)
# CORS 설정 — 개발: localhost:5173, 운영: 실제 도메인
app.add_middleware(
CORSMiddleware,
allow_origins=settings.ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(users.router, prefix="/api/users", tags=["users"])
app.include_router(items.router, prefix="/api/items", tags=["items"])
@app.get("/api/health")
async def health_check():
return {"status": "ok"}
2단계: SvelteKit 프론트엔드 연동
SvelteKit에서 FastAPI 백엔드를 호출할 때 가장 중요한 포인트는 환경변수 관리와 서버/클라이언트 분리입니다. SvelteKit은 SSR 환경에서 실행되는 코드와 브라우저에서 실행되는 코드가 다를 수 있기 때문에, API 호출 위치를 명확히 구분해야 합니다.
일반적으로 인증이 필요한 API 호출은 SvelteKit의 +page.server.ts 또는 +server.ts에서 처리하고, 공개 데이터는 클라이언트에서 직접 호출하는 패턴을 사용합니다.
// src/lib/api.ts — API 클라이언트 유틸리티
const BASE_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:8000';
export async function apiFetch<T>(
path: string,
options: RequestInit = {}
): Promise<T> {
const token = localStorage.getItem('access_token');
const res = await fetch(`${BASE_URL}${path}`, {
...options,
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
...options.headers,
},
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err.detail ?? `HTTP ${res.status}`);
}
return res.json() as Promise<T>;
}
서버사이드 데이터 로딩
SvelteKit의 load 함수를 활용하면 페이지 렌더링 전에 서버에서 데이터를 미리 가져와 SEO에 유리한 구조를 만들 수 있습니다. 아래는 상품 목록 페이지에서 FastAPI로부터 데이터를 SSR 방식으로 로드하는 예제입니다.
// src/routes/items/+page.server.ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ fetch, url }) => {
const page = url.searchParams.get('page') ?? '1';
const res = await fetch(
`${process.env.API_URL}/api/items?page=${page}&limit=20`
);
if (!res.ok) {
return { items: [], total: 0 };
}
const data = await res.json();
return {
items: data.items,
total: data.total,
currentPage: Number(page),
};
};
3단계: AWS Lightsail + Vercel 배포 전략
FastAPI → AWS Lightsail
FastAPI 백엔드는 AWS Lightsail의 Linux 인스턴스(월 $5~$10)에 배포하는 것이 비용 효율적입니다. systemd 서비스로 등록해두면 서버 재시작 시에도 자동으로 앱이 실행됩니다. Nginx를 리버스 프록시로 앞에 두어 SSL 종료와 로드 분산을 처리합니다.
# /etc/systemd/system/myapi.service
[Unit]
Description=FastAPI Application
After=network.target
[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/backend
Environment="PATH=/home/ubuntu/backend/.venv/bin"
ExecStart=/home/ubuntu/backend/.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 2
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
SvelteKit → Vercel
SvelteKit 프론트엔드는 Vercel에 배포하는 것이 가장 간단합니다. @sveltejs/adapter-vercel을 설치하고, Vercel 대시보드에서 환경변수(VITE_API_URL)를 설정하면 됩니다. Vercel의 Preview 배포 기능을 활용하면 PR마다 자동으로 스테이징 환경이 생성되어 QA가 쉬워집니다.
# svelte.config.js
import adapter from '@sveltejs/adapter-vercel';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter({
runtime: 'nodejs20.x',
}),
},
};
export default config;
4단계: 인증 처리 — JWT + HttpOnly 쿠키
풀스택 서비스에서 가장 민감한 부분이 인증입니다. 보안을 위해 Access Token은 메모리(Svelte store)에, Refresh Token은 HttpOnly 쿠키에 저장하는 패턴을 권장합니다. FastAPI의 Response 객체를 활용해 쿠키를 직접 설정할 수 있습니다.
# FastAPI — 로그인 시 Refresh Token을 HttpOnly 쿠키로 설정
from fastapi import APIRouter, Response
from app.core.security import create_access_token, create_refresh_token
router = APIRouter()
@router.post("/login")
async def login(credentials: LoginRequest, response: Response):
user = await authenticate_user(credentials.email, credentials.password)
access_token = create_access_token({"sub": str(user.id)})
refresh_token = create_refresh_token({"sub": str(user.id)})
# Refresh Token은 HttpOnly 쿠키로
response.set_cookie(
key="refresh_token",
value=refresh_token,
httponly=True,
secure=True, # HTTPS 전용
samesite="lax",
max_age=60 * 60 * 24 * 30, # 30일
)
return {"access_token": access_token, "token_type": "bearer"}
5단계: 운영 중 자주 만나는 문제와 해결법
CORS 오류
개발 환경에서 SvelteKit(5173)이 FastAPI(8000)를 호출할 때 CORS 오류가 발생합니다. FastAPI의 CORSMiddleware에서 allow_origins에 http://localhost:5173을 추가하면 해결됩니다. 운영 환경에서는 환경변수로 도메인을 주입하세요.
SvelteKit SSR에서 fetch 실패
서버 컴포넌트에서 fetch를 사용할 때 브라우저의 쿠키가 자동으로 전달되지 않는 경우가 있습니다. SvelteKit의 load 함수에 주입되는 fetch는 내부적으로 쿠키를 전달해주므로, 직접 node-fetch나 global fetch 대신 SvelteKit이 제공하는 fetch를 사용해야 합니다.
Lightsail 메모리 부족
$5 플랜(512MB RAM)에서 uvicorn을 --workers 4로 실행하면 OOM이 발생할 수 있습니다. 소규모 서비스는 --workers 2로 시작하고, 트래픽에 따라 스왑 메모리를 추가하거나 $10 플랜으로 업그레이드하세요.
마치며
FastAPI + SvelteKit 조합은 빠른 개발 속도, 타입 안전성, 그리고 배포 유연성을 동시에 제공하는 강력한 풀스택 선택지입니다. FastAPI의 자동 문서화와 SvelteKit의 파일 기반 라우팅은 초기 개발 단계에서 생산성을 극적으로 높여주고, AWS Lightsail과 Vercel의 조합은 비용 효율적인 운영을 가능하게 합니다.
코드벤터는 이런 실전 기술 스택을 직접 서비스에 적용하며 얻은 경험을 지속적으로 공유합니다. 다음 포스트에서는 GitHub Actions를 활용한 CI/CD 자동화와 무중단 배포 전략을 더 자세히 다룰 예정이니 기대해 주세요. 궁금한 점이 있다면 댓글로 남겨주세요!


