Supabase 완벽 가이드 | Firebase 대안·PostgreSQL·Auth·Storage·실시간·Edge Functions

Supabase 완벽 가이드 | Firebase 대안·PostgreSQL·Auth·Storage·실시간·Edge Functions

이 글의 핵심

Supabase로 풀스택 앱을 구축하는 완벽 가이드입니다. 오픈소스 Firebase 대안으로 PostgreSQL, Auth, Storage, Realtime, Edge Functions까지 실전 예제로 정리했습니다.

실무 경험 공유: Firebase에서 Supabase로 전환하면서, 데이터베이스 유연성이 향상되고 비용이 60% 절감된 경험을 공유합니다.

들어가며: “Firebase는 비싸요”

실무 문제 시나리오

시나리오 1: 복잡한 쿼리가 필요해요
Firestore는 제한적입니다. Supabase는 PostgreSQL을 사용합니다.

시나리오 2: 비용이 너무 높아요
Firebase는 비쌉니다. Supabase는 오픈소스로 저렴합니다.

시나리오 3: 벤더 락인이 걱정돼요
Firebase는 종속성이 높습니다. Supabase는 표준 PostgreSQL을 사용합니다.


1. Supabase란?

핵심 특징

Supabase는 오픈소스 Firebase 대안입니다.

주요 기능:

  • PostgreSQL: 강력한 관계형 DB
  • Auth: 이메일, OAuth, Magic Link
  • Storage: 파일 업로드
  • Realtime: 실시간 구독
  • Edge Functions: Deno 기반 서버리스
  • Row Level Security: 보안

2. 프로젝트 설정

설치

npm install @supabase/supabase-js

초기화

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

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;

export const supabase = createClient(supabaseUrl, supabaseKey);

3. 데이터베이스 (PostgreSQL)

테이블 생성

-- SQL Editor에서 실행
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  email TEXT UNIQUE NOT NULL,
  name TEXT,
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE posts (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  title TEXT NOT NULL,
  content TEXT,
  author_id UUID REFERENCES users(id),
  published BOOLEAN DEFAULT FALSE,
  created_at TIMESTAMP DEFAULT NOW()
);

CRUD

// Create
const { data, error } = await supabase
  .from('users')
  .insert({ email: '[email protected]', name: 'John' })
  .select()
  .single();

// Read
const { data: users } = await supabase
  .from('users')
  .select('*')
  .order('created_at', { ascending: false });

// Read with Join
const { data: posts } = await supabase
  .from('posts')
  .select(`
    *,
    author:users(name, email)
  `)
  .eq('published', true);

// Update
const { data } = await supabase
  .from('users')
  .update({ name: 'John Updated' })
  .eq('id', userId)
  .select();

// Delete
const { error } = await supabase
  .from('users')
  .delete()
  .eq('id', userId);

4. 인증 (Auth)

이메일 회원가입

const { data, error } = await supabase.auth.signUp({
  email: '[email protected]',
  password: 'password123',
});

로그인

const { data, error } = await supabase.auth.signInWithPassword({
  email: '[email protected]',
  password: 'password123',
});

OAuth

const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
});

세션 관리

// 현재 사용자
const { data: { user } } = await supabase.auth.getUser();

// 로그아웃
await supabase.auth.signOut();

// 세션 변경 감지
supabase.auth.onAuthStateChange((event, session) => {
  console.log('Auth event:', event, session);
});

5. Storage

파일 업로드

const file = event.target.files[0];

const { data, error } = await supabase.storage
  .from('avatars')
  .upload(`public/${userId}/${file.name}`, file);

// 공개 URL
const { data: { publicUrl } } = supabase.storage
  .from('avatars')
  .getPublicUrl(`public/${userId}/${file.name}`);

파일 다운로드

const { data, error } = await supabase.storage
  .from('avatars')
  .download(`public/${userId}/avatar.png`);

6. Realtime

구독

const channel = supabase
  .channel('posts')
  .on(
    'postgres_changes',
    {
      event: '*',
      schema: 'public',
      table: 'posts',
    },
    (payload) => {
      console.log('Change:', payload);
    }
  )
  .subscribe();

// 구독 해제
channel.unsubscribe();

React 예제

import { useEffect, useState } from 'react';
import { supabase } from './lib/supabase';

export default function Posts() {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    // 초기 데이터
    fetchPosts();

    // 실시간 구독
    const channel = supabase
      .channel('posts')
      .on(
        'postgres_changes',
        { event: '*', schema: 'public', table: 'posts' },
        () => {
          fetchPosts();
        }
      )
      .subscribe();

    return () => {
      channel.unsubscribe();
    };
  }, []);

  async function fetchPosts() {
    const { data } = await supabase.from('posts').select('*');
    setPosts(data || []);
  }

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

7. Row Level Security (RLS)

정책 설정

-- RLS 활성화
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- 읽기: 모두 가능
CREATE POLICY "Public posts are viewable by everyone"
ON posts FOR SELECT
USING (published = true);

-- 쓰기: 본인만 가능
CREATE POLICY "Users can create their own posts"
ON posts FOR INSERT
WITH CHECK (auth.uid() = author_id);

CREATE POLICY "Users can update their own posts"
ON posts FOR UPDATE
USING (auth.uid() = author_id);

8. Edge Functions

함수 생성

npx supabase functions new hello
// supabase/functions/hello/index.ts
import { serve } from 'https://deno.land/[email protected]/http/server.ts';

serve(async (req) => {
  const { name } = await req.json();

  return new Response(
    JSON.stringify({ message: `Hello ${name}!` }),
    { headers: { 'Content-Type': 'application/json' } }
  );
});

배포

npx supabase functions deploy hello

호출

const { data, error } = await supabase.functions.invoke('hello', {
  body: { name: 'John' },
});

정리 및 체크리스트

핵심 요약

  • Supabase: 오픈소스 Firebase 대안
  • PostgreSQL: 강력한 관계형 DB
  • Auth: 다양한 인증 방식
  • Storage: 파일 관리
  • Realtime: 실시간 구독
  • RLS: 보안 정책

구현 체크리스트

  • 프로젝트 생성
  • 테이블 설계
  • CRUD 구현
  • Auth 설정
  • Storage 구현
  • Realtime 구독
  • RLS 정책 설정

같이 보면 좋은 글

  • Firebase 완벽 가이드
  • Prisma 완벽 가이드
  • Next.js App Router 가이드

이 글에서 다루는 키워드

Supabase, Firebase, PostgreSQL, Auth, Storage, Realtime, Backend

자주 묻는 질문 (FAQ)

Q. Firebase와 비교하면 어떤가요?

A. Supabase는 PostgreSQL 기반으로 복잡한 쿼리가 가능하고 비용이 저렴합니다. Firebase는 더 간단하지만 제한적입니다.

Q. 무료로 사용할 수 있나요?

A. 네, 무료 플랜이 있습니다. 500MB DB, 1GB Storage, 50K MAU까지 무료입니다.

Q. 셀프 호스팅이 가능한가요?

A. 네, 오픈소스로 Docker Compose로 셀프 호스팅이 가능합니다.

Q. 프로덕션에서 사용해도 되나요?

A. 네, 많은 스타트업과 기업에서 안정적으로 사용하고 있습니다.

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3