Supabase는 PostgreSQL + 인증 + RLS(Row Level Security)를 클라이언트에서 직접 쓸 수 있게 해줍니다. 그런데 모든 로직을 Supabase에 맡기면 복잡한 비즈니스 로직, 외부 API 연동, 백그라운드 작업을 처리하기 어렵습니다.
FastAPI를 함께 쓰면 두 가지를 분리할 수 있습니다. 이 글에서는 twojaking.com 코인 모의투자 서비스를 구축하면서 정착된 분기 기준과 실제 코드 구조를 설명합니다.
분기 기준
단순 조회/저장 → 프론트엔드 → Supabase 직접 호출
복잡한 로직 → 프론트엔드 → FastAPI → Supabase
Supabase 직접 호출이 적합한 경우:
- 사용자 프로필 조회
- 단순 리스트 조회 (뉴스 목록, 기사 목록)
- 소셜 로그인 / 세션 관리
- RLS로 충분히 보안이 보장되는 CRUD
FastAPI 경유가 필요한 경우:
- 거래 체결 (잔액 차감 + 포지션 생성을 트랜잭션으로 처리)
- 레버리지 청산 로직 (가격 모니터링 + 자동 청산)
- 퀘스트·레벨·티어 계산 (복잡한 비즈니스 규칙)
- SSE 가격 스트림 (Redis 기반 실시간 push)
- 외부 API 연동 (Binance, LLM)
- 백그라운드 Worker 트리거
판단 기준을 한 문장으로 요약하면: 여러 테이블을 원자적으로 변경해야 하거나, 외부 시스템과 통신이 필요하거나, RLS만으로 보안을 보장할 수 없는 경우 FastAPI를 경유합니다.
FastAPI 서버 구조
app/main.py에서 아키텍처의 역할 분리가 명시적으로 드러납니다.
# app/main.py
"""
Supabase + FastAPI 하이브리드 아키텍처
- Supabase: 인증, 기본 CRUD, 데이터베이스 (PostgreSQL + RLS)
- FastAPI: 복잡한 비즈니스 로직, 외부 API 통합, 데이터 분석
"""
app = FastAPI(
title=f"{settings.PROJECT_NAME} (Hybrid)",
description="Supabase + FastAPI 하이브리드 아키텍처 - 복잡한 비즈니스 로직 전용",
)
# 미들웨어 적용 순서 (역순으로 처리됨)
# 요청: CORS → Logging → TokenRefresh → ErrorHandler → Route
# 응답: Route → ErrorHandler → TokenRefresh → Logging → CORS
app.add_middleware(ErrorHandlerMiddleware) # 1. 예외 처리
app.add_middleware(TokenRefreshMiddleware) # 2. 토큰 갱신 (응답 후 쿠키 설정)
app.add_middleware(LoggingMiddleware) # 3. 요청/응답 로깅
app.add_middleware(
CORSMiddleware,
allow_origins=settings.CORS_ORIGINS_LIST,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
) # 4. CORS (가장 먼저 요청 처리)
FastAPI는 미들웨어를 역순으로 적용합니다. 마지막에 추가한 미들웨어가 요청을 가장 먼저 받습니다. CORS를 마지막에 추가하는 이유가 여기에 있습니다.
백그라운드 작업은 이 FastAPI 인스턴스에 없습니다. 가격 수집, 청산 모니터, 퀘스트 모니터는 별도 Worker 프로세스에서 실행됩니다. API 서버는 HTTP 요청 처리만 담당합니다.
프론트엔드 클라이언트 — camelCase 자동 변환
FastAPI는 Python 관례에 따라 snake_case로 응답합니다. TypeScript는 camelCase를 사용합니다. 이 불일치를 매 API 호출마다 수동으로 변환하면 코드가 지저분해집니다.
app/lib/fastApi.ts에서 axios 인터셉터로 이 변환을 한 곳에서 처리합니다.
// app/lib/fastApi.ts
import axios from "axios";
import { camelToSnake, snakeToCamel } from "~/utils/lodash";
export const fastApi = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
withCredentials: true, // 쿠키 자동 전송
});
// 요청 인터셉터: camelCase → snake_case
fastApi.interceptors.request.use((config) => {
if (config.data !== null && typeof config.data === "object") {
config.data = camelToSnake(config.data);
}
return config;
});
// 응답 인터셉터: snake_case → camelCase
fastApi.interceptors.response.use((response) => {
if (response.data !== null && typeof response.data === "object") {
response.data = snakeToCamel(response.data);
}
return response;
});
이 인터셉터 덕분에 프론트엔드 코드에서는 항상 camelCase로만 접근하면 됩니다.
// API 응답 { asset_symbol: "BTC", avg_buy_price: 95000 }
// 인터셉터 통과 후 → { assetSymbol: "BTC", avgBuyPrice: 95000 }
const { assetSymbol, avgBuyPrice } = response.data;
withCredentials: true는 쿠키 기반 인증에 필요합니다. FastAPI의 TokenRefreshMiddleware가 응답 시 세션 쿠키를 갱신하는데, 이 쿠키가 자동으로 다음 요청에 포함됩니다.
Supabase 클라이언트
// app/lib/supabase.ts
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY,
{
auth: {
persistSession: false, // localStorage에 토큰 저장 안 함
},
}
);
persistSession: false로 설정한 이유가 있습니다. 인증 토큰을 localStorage에 저장하면 XSS 공격에 취약합니다. 세션 관리는 FastAPI의 쿠키 기반 방식으로 일원화했습니다.
전체 기술 스택
| 레이어 | 기술 | 역할 |
|---|---|---|
| 프론트엔드 | React + TypeScript + React Router | UI, 상태 관리 |
| API 서버 | FastAPI | 복잡한 비즈니스 로직 |
| DB/인증 | Supabase (PostgreSQL + Auth) | 데이터 저장, RLS, 소셜 로그인 |
| 캐시/실시간 | Redis | 가격 캐시, 청산 트리거, 채팅 |
| 배치 처리 | Worker 프로세스 | 가격 수집, 청산 모니터 |
| 외부 데이터 | Binance API | 시세 데이터 |
| AI | LM Studio (로컬 LLM) | 뉴스 분류, 클러스터링 |
정리
FastAPI + Supabase 하이브리드 구조의 핵심은 분기 기준을 명확히 하는 것입니다.
- 단순하면 Supabase 직접: RLS가 보안을 보장하고 비즈니스 로직이 단순한 경우
- 복잡하면 FastAPI 경유: 트랜잭션, 외부 API, 백그라운드 연동이 필요한 경우
axios 인터셉터로 snake_case ↔ camelCase 변환을 한 곳에서 처리하면 프론트엔드 코드 전체에서 일관성을 유지할 수 있습니다.