Supabase로 프론트엔드 개발자도 쉽게 백엔드 구축하기

Khan·2025년 5월 2일
1

Supabase란?

Supabase는 "오픈소스 Firebase 대안"으로 알려진 백엔드 서비스 플랫폼입니다. 2020년에 출시된 이후 빠르게 성장하고 있는 Supabase는 Firebase와 유사한 기능을 제공하지만, NoSQL 대신 PostgreSQL이라는 관계형 데이터베이스를 기반으로 합니다.

제가 최근 개발한 AI 블로그 관리 시스템 프로젝트에서도 Supabase를 활용했는데요, 프론트엔드 개발자로서 별도의 백엔드 서버 구축 없이 강력한 백엔드 기능을 구현할 수 있어 매우 효율적이었습니다.

PostgreSQL vs NoSQL: 왜 Supabase가 다른가?

Firebase와 Supabase의 가장 큰 차이점은 데이터베이스 모델입니다.

Firebase (NoSQL)

  • 유연한 스키마로 구조 정의 없이 데이터 저장 가능
  • JSON 형태의 문서로 데이터 저장
  • 테이블 간 명시적 관계(외래 키) 정의 없음
  • 복잡한 조인이나 트랜잭션에 약점

Supabase (PostgreSQL)

  • 25년 넘게 개발된 검증된 관계형 데이터베이스
  • 데이터 간의 관계를 명확하게 정의하고 관리
  • JSON, 배열, 위치 정보 등 다양한 데이터 타입 지원
  • 트랜잭션 기능으로 데이터 무결성 보장
  • 세밀한 접근 권한 제어 가능

프로젝트에서 Supabase 활용 사례

1. 클라이언트 설정

우선 프로젝트에서는 다음과 같이 Supabase 클라이언트를 설정했습니다:

// src/lib/supabase.ts
import { createClient } from "@supabase/supabase-js";

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

export const supabase = createClient(supabaseUrl || "", supabaseAnonKey || "");

2. 인증 시스템 구현

Supabase의 가장 큰 장점 중 하나는 인증 시스템을 쉽게 구현할 수 있다는 점입니다. 프로젝트에서는 다음과 같이 로그인, 로그아웃, 세션 관리 등의 기능을 구현했습니다:

// 로그인 API 호출
export async function loginUser(email: string, password: string) {
  const response = await fetch("/api/auth/login", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ email, password }),
  });

  const result = await response.json();
  
  if (result.data?.session) {
    await supabase.auth.setSession({
      access_token: result.data.session.access_token,
      refresh_token: result.data.session.refresh_token,
    });
  }

  return result.data;
}

// 인증 상태 변경 리스너 설정
export function setupAuthStateChangeListener(callback) {
  return supabase.auth.onAuthStateChange((event, session) => {
    callback(session);
  });
}

이 코드를 활용해 useAuth 훅을 만들어 전체 애플리케이션에서 인증 상태를 관리했습니다:

// src/hooks/useAuth.ts (간략화 버전)
export function useAuth() {
  const [user, setUser] = useState(null);
  const [isAdmin, setIsAdmin] = useState(false);
  
  useEffect(() => {
    // 세션 확인 및 상태 관리
    const checkSession = async () => {
      const { data: { session } } = await getCurrentSession();
      
      if (session) {
        setUser(session.user);
        const { data } = await getUserRole(session.user.id);
        setIsAdmin(data?.role === "admin");
      }
    };
    
    checkSession();
    
    // 인증 상태 변경 감지
    const { data: { subscription } } = setupAuthStateChangeListener(async (session) => {
      // 세션 변경 시 상태 업데이트
    });
    
    return () => subscription.unsubscribe();
  }, []);
  
  // 로그인, 로그아웃 등 함수 제공
  return { user, isAdmin, login, logout, register };
}

3. 데이터베이스 쿼리 예시

Supabase는 직관적인 API를 통해 데이터베이스 작업을 쉽게 수행할 수 있습니다:

// 사용자 목록 조회 예시
const { data: users, error } = await supabase
  .from("users")
  .select("id, name, email, blog_id, phone, bank_name")
  .eq("role", "user")
  .order("created_at", { ascending: false });

// 블로그 글 상태 업데이트 예시
const { data, error } = await supabase
  .from("blog_posts")
  .update({
    status: "completed",
    blog_url: blogUrl,
    completion_notes: completionNotes,
    completed_at: new Date(),
  })
  .eq("id", postId)
  .eq("assigned_to", user.id)
  .select();

4. 파일 스토리지 활용

프로젝트에서는 블로그 이미지를 저장하고 관리하기 위해 Supabase Storage를 활용했습니다:

// 이미지 업로드 예시
const filePath = `blog_images/${postId}/${fileName}`;
const { data, error: uploadError } = await supabase.storage
  .from("uploads")
  .upload(filePath, fileData, {
    contentType: file.type,
    cacheControl: "3600",
  });

// 이미지 URL 생성
const { data: urlData } = await supabase.storage
  .from("uploads")
  .getPublicUrl(filePath);

// 이미지 삭제
const { error: storageError } = await supabase.storage
  .from("uploads")
  .remove([imageData.file_path]);

5. 서버리스 함수와 RPC

복잡한 트랜잭션이나 비즈니스 로직은 PostgreSQL의 함수를 정의하고 RPC로 호출할 수 있습니다:

// Supabase RPC 호출 예시
const { data, error } = await supabase.rpc(
  "approve_blog_with_points",
  {
    p_post_id: postId,
    p_admin_id: user.id,
    p_admin_feedback: adminFeedback || "",
    p_points_amount: pointsAmount,
  }
);

이 예시에서는 블로그 글 승인과 포인트 지급이라는 복잡한 로직을 데이터베이스 함수로 처리하고 있습니다.

6. Row Level Security(RLS) 활용

프로젝트에서는 사용자 권한에 따라 데이터 접근을 제한하기 위해 RLS를 활용했습니다:

// 역할 기반 접근 제어 예시
export async function verifyUserRole(request, requiredRole = "admin") {
  // 사용자 인증 및 역할 확인
  const { data: userData } = await supabase
    .from("users")
    .select("role")
    .eq("id", user.id)
    .single();
    
  if (userData.role !== requiredRole) {
    return {
      user: null,
      errorResponse: NextResponse.json(
        { error: "권한이 없습니다." },
        { status: 403 },
      ),
    };
  }
  
  // 인증 성공 시 사용자 정보 반환
  return { user, errorResponse: null };
}

Supabase를 선택한 이유

  1. 프론트엔드 개발자 친화적: 백엔드 지식이 많지 않아도 쉽게 사용할 수 있습니다.
  2. 오픈소스: 코드가 공개되어 있어 투명성이 높고 필요시 직접 수정할 수 있습니다.
  3. 관계형 데이터베이스: 복잡한 데이터 관계를 잘 표현할 수 있는 PostgreSQL을 사용합니다.
  4. 통합 서비스: 인증, 데이터베이스, 스토리지, 서버리스 함수 등을 하나의 플랫폼에서 제공합니다.
  5. 벤더 종속 없음: 표준 기술(SQL)을 사용하기 때문에 나중에 다른 서비스로 이전하기 쉽습니다.

Supabase의 장단점

장점

  • 프론트엔드 개발자도 쉽게 백엔드 기능 구현 가능
  • 강력한 관계형 데이터베이스의 장점을 활용 가능
  • 인증, 스토리지, 실시간 데이터 등 필수 기능 통합 제공
  • 예측 가능한 가격 정책
  • 오픈소스로 투명성 보장

단점

  • 비교적 새로운 서비스로 Firebase보다 생태계가 작음
  • SQL 지식이 필요함 (물론 이건 장점이 될 수도 있습니다)
  • 셀프 호스팅 시 복잡도가 높음
  • Edge Functions 기능이 아직 제한적

마치며

프론트엔드 개발자로서 Supabase는 백엔드에 대한 부담 없이 풀스택 애플리케이션을 개발할 수 있게 해주는 강력한 도구입니다. 특히 관계형 데이터베이스의 장점을 활용하면서도 Firebase와 같은 편리한 API를 제공하는 점이 매력적입니다.

이번 AI 블로그 관리 시스템 프로젝트에서는 Supabase를 통해 인증 시스템, 데이터베이스 CRUD 작업, 파일 스토리지, 권한 관리 등을 모두 구현했으며, 백엔드 서버 구축 없이도 완전한 기능을 갖춘 애플리케이션을 개발할 수 있었습니다.

복잡한 데이터 관계가 있는 프로젝트나, 백엔드 개발 리소스가 제한적인 소규모 팀, 그리고 빠른 MVP 개발이 필요한 스타트업에 Supabase는 훌륭한 선택이 될 것입니다.

profile
끄적끄적 🖋️

0개의 댓글