← 블로그 목록
FastAPISupabase아키텍처ReactTypeScript하이브리드백엔드

FastAPI + Supabase 하이브리드 아키텍처 — 언제 직접 쿼리하고 언제 API를 쓸까

2025년 11월 25일

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 RouterUI, 상태 관리
API 서버FastAPI복잡한 비즈니스 로직
DB/인증Supabase (PostgreSQL + Auth)데이터 저장, RLS, 소셜 로그인
캐시/실시간Redis가격 캐시, 청산 트리거, 채팅
배치 처리Worker 프로세스가격 수집, 청산 모니터
외부 데이터Binance API시세 데이터
AILM Studio (로컬 LLM)뉴스 분류, 클러스터링

정리

FastAPI + Supabase 하이브리드 구조의 핵심은 분기 기준을 명확히 하는 것입니다.

  • 단순하면 Supabase 직접: RLS가 보안을 보장하고 비즈니스 로직이 단순한 경우
  • 복잡하면 FastAPI 경유: 트랜잭션, 외부 API, 백그라운드 연동이 필요한 경우

axios 인터셉터로 snake_case ↔ camelCase 변환을 한 곳에서 처리하면 프론트엔드 코드 전체에서 일관성을 유지할 수 있습니다.

0

댓글 0

Ctrl+Enter