🗄️ 데이터베이스

Supabase

"수파베이스"로 발음

오픈소스 Firebase 대안으로, PostgreSQL을 기반으로 실시간 데이터 구독, Row Level Security(RLS), Edge Functions, 인증 등을 제공하는 BaaS(Backend-as-a-Service) 플랫폼입니다.

📖 상세 설명

Supabase는 2020년 Y Combinator 출신 스타트업이 만든 오픈소스 BaaS로, "Firebase의 오픈소스 대안"을 표방합니다. Firebase와 달리 표준 PostgreSQL을 데이터베이스로 사용하여 SQL의 모든 기능(JOIN, Transaction, 복합 쿼리)을 활용할 수 있고, Vendor Lock-in 없이 언제든 셀프 호스팅으로 전환할 수 있습니다.

핵심 차별점

PostgreSQL 기반이므로 표준 SQL, Foreign Key, 복합 인덱스, 전체 텍스트 검색 등 관계형 DB의 모든 기능을 BaaS 환경에서 사용 가능합니다.

Supabase 핵심 기능

🐘
PostgreSQL DB
표준 SQL, pgvector 지원
Realtime
DB 변경 실시간 구독
🔐
Auth
소셜 로그인, Magic Link
🛡️
RLS (Row Level Security)
행 단위 접근 제어
📦
Storage
S3 호환 파일 저장소
🌐
Edge Functions
Deno 기반 서버리스

Supabase vs Firebase 비교

특성 Supabase Firebase
데이터베이스 PostgreSQL (관계형) Firestore (Document DB)
쿼리 언어 SQL (JOIN, 서브쿼리 가능) 독자 API (제한적 쿼리)
오픈소스 완전 오픈소스, 셀프 호스팅 가능 클로즈드 소스
보안 모델 PostgreSQL RLS (SQL 기반) Security Rules (독자 문법)
실시간 PostgreSQL LISTEN/NOTIFY Firestore onSnapshot
Vector 검색 pgvector 네이티브 지원 지원 안 함

💻 코드 예제

1. Supabase 클라이언트 초기화 및 CRUD

JavaScript/TypeScript
import { createClient } from '@supabase/supabase-js'; const supabase = createClient( 'https://your-project.supabase.co', 'your-anon-key' ); // CREATE - 데이터 삽입 async function createPost(post) { const { data, error } = await supabase .from('posts') .insert({ title: post.title, content: post.content, author_id: post.authorId, created_at: new Date().toISOString() }) .select() .single(); if (error) throw error; return data; } // READ - 조건부 조회 (SQL WHERE처럼) async function getPosts(filters) { let query = supabase .from('posts') .select(` id, title, content, created_at, author:users!author_id ( id, name, avatar_url ), comments ( id, content ) `) .eq('published', true); if (filters.category) { query = query.eq('category', filters.category); } if (filters.search) { query = query.ilike('title', `%${filters.search}%`); } const { data, error } = await query .order('created_at', { ascending: false }) .range(0, 9); // LIMIT 10 if (error) throw error; return data; } // UPDATE - 데이터 수정 async function updatePost(postId, updates) { const { data, error } = await supabase .from('posts') .update({ title: updates.title, content: updates.content, updated_at: new Date().toISOString() }) .eq('id', postId) .select() .single(); if (error) throw error; return data; } // DELETE - 데이터 삭제 async function deletePost(postId) { const { error } = await supabase .from('posts') .delete() .eq('id', postId); if (error) throw error; }

2. Row Level Security (RLS) 정책

SQL (Supabase SQL Editor)
-- 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 "Authenticated users can create posts" ON posts FOR INSERT TO authenticated WITH CHECK (auth.uid() = author_id); -- 본인 게시글만 수정 가능 CREATE POLICY "Users can update own posts" ON posts FOR UPDATE TO authenticated USING (auth.uid() = author_id) WITH CHECK (auth.uid() = author_id); -- 본인 게시글만 삭제 가능 CREATE POLICY "Users can delete own posts" ON posts FOR DELETE TO authenticated USING (auth.uid() = author_id); -- 조직 멤버만 접근 가능한 테이블 CREATE POLICY "Organization members only" ON org_documents FOR ALL TO authenticated USING ( org_id IN ( SELECT org_id FROM org_members WHERE user_id = auth.uid() ) );

3. Realtime 실시간 구독

JavaScript
// 테이블 변경 실시간 구독 function subscribeToMessages(roomId, onMessage) { const channel = supabase .channel(`room:${roomId}`) .on( 'postgres_changes', { event: '*', // INSERT, UPDATE, DELETE schema: 'public', table: 'messages', filter: `room_id=eq.${roomId}` }, (payload) => { console.log('Change received:', payload); switch (payload.eventType) { case 'INSERT': onMessage({ type: 'new', data: payload.new }); break; case 'UPDATE': onMessage({ type: 'updated', data: payload.new }); break; case 'DELETE': onMessage({ type: 'deleted', data: payload.old }); break; } } ) .subscribe(); // 구독 해제 함수 반환 return () => { supabase.removeChannel(channel); }; } // React에서 사용 function ChatRoom({ roomId }) { const [messages, setMessages] = useState([]); useEffect(() => { // 초기 데이터 로드 loadMessages(); // 실시간 구독 const unsubscribe = subscribeToMessages(roomId, (event) => { if (event.type === 'new') { setMessages(prev => [...prev, event.data]); } }); return unsubscribe; }, [roomId]); }

4. Supabase Auth 인증

JavaScript
// 이메일/비밀번호 회원가입 async function signUp(email, password, metadata) { const { data, error } = await supabase.auth.signUp({ email, password, options: { data: { full_name: metadata.fullName, avatar_url: metadata.avatarUrl } } }); if (error) throw error; return data.user; } // OAuth 소셜 로그인 (Google, GitHub 등) async function signInWithGoogle() { const { data, error } = await supabase.auth.signInWithOAuth({ provider: 'google', options: { redirectTo: `${window.location.origin}/auth/callback` } }); if (error) throw error; } // Magic Link (비밀번호 없는 로그인) async function signInWithMagicLink(email) { const { error } = await supabase.auth.signInWithOtp({ email, options: { emailRedirectTo: `${window.location.origin}/dashboard` } }); if (error) throw error; } // 세션 상태 감지 supabase.auth.onAuthStateChange((event, session) => { console.log('Auth event:', event); if (event === 'SIGNED_IN') { console.log('User signed in:', session.user); } else if (event === 'SIGNED_OUT') { console.log('User signed out'); } else if (event === 'TOKEN_REFRESHED') { console.log('Token refreshed'); } });

5. Edge Functions (Deno 서버리스)

TypeScript (supabase/functions/hello/index.ts)
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; serve(async (req) => { // CORS 헤더 const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', }; if (req.method === 'OPTIONS') { return new Response('ok', { headers: corsHeaders }); } try { // 환경 변수에서 Supabase 설정 const supabaseClient = createClient( Deno.env.get('SUPABASE_URL') ?? '', Deno.env.get('SUPABASE_ANON_KEY') ?? '', { global: { headers: { Authorization: req.headers.get('Authorization')! } } } ); // 인증된 사용자 확인 const { data: { user }, error: authError } = await supabaseClient.auth.getUser(); if (authError || !user) { return new Response( JSON.stringify({ error: 'Unauthorized' }), { status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } ); } // 요청 처리 const { action, data } = await req.json(); // 비즈니스 로직 (외부 API 호출 등) const result = await processAction(action, data, user); return new Response( JSON.stringify(result), { headers: { ...corsHeaders, 'Content-Type': 'application/json' } } ); } catch (error) { return new Response( JSON.stringify({ error: error.message }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } ); } });

6. pgvector로 AI 임베딩 검색

SQL + JavaScript
-- pgvector 확장 활성화 CREATE EXTENSION IF NOT EXISTS vector; -- 임베딩 컬럼이 있는 테이블 CREATE TABLE documents ( id BIGSERIAL PRIMARY KEY, content TEXT, embedding vector(1536), -- OpenAI ada-002 차원 metadata JSONB ); -- 인덱스 생성 (HNSW 또는 IVFFlat) CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops); -- 유사도 검색 함수 CREATE OR REPLACE FUNCTION match_documents( query_embedding vector(1536), match_threshold FLOAT, match_count INT ) RETURNS TABLE ( id BIGINT, content TEXT, similarity FLOAT ) LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY SELECT documents.id, documents.content, 1 - (documents.embedding <=> query_embedding) AS similarity FROM documents WHERE 1 - (documents.embedding <=> query_embedding) > match_threshold ORDER BY documents.embedding <=> query_embedding LIMIT match_count; END; $$; -- JavaScript에서 사용 async function searchSimilarDocuments(queryText) { // OpenAI로 쿼리 임베딩 생성 const embedding = await openai.embeddings.create({ model: 'text-embedding-ada-002', input: queryText }); // Supabase RPC로 유사도 검색 const { data, error } = await supabase.rpc('match_documents', { query_embedding: embedding.data[0].embedding, match_threshold: 0.7, match_count: 5 }); return data; }

💬 현업 대화 예시

백엔드 개발자
"Supabase 쓰면 PostgreSQL 그대로라 SQL 복잡한 쿼리도 다 돼. JOIN이나 서브쿼리 자유롭게 쓸 수 있어서 Firebase보다 데이터 모델링 훨씬 편해."
풀스택 개발자
"RLS 좋긴 한데 처음에 좀 헷갈려. 정책 잘못 쓰면 데이터가 아예 안 보이거나 전부 노출될 수 있으니까 테스트 꼼꼼히 해야 해."
AI 엔지니어
"pgvector 네이티브 지원이라 RAG 파이프라인 만들기 딱이야. 임베딩 저장하고 유사도 검색까지 Supabase 하나로 끝나니까."
테크 리드
"셀프 호스팅도 가능해서 나중에 빠져나올 수 있다는 게 큰 장점이야. Vendor Lock-in 걱정 없이 시작할 수 있거든."

⚠️ 주의사항

RLS 정책 테스트 필수

Row Level Security는 강력하지만 복잡합니다. 잘못된 정책은 데이터 유출이나 완전한 차단을 일으킬 수 있으니, supabase test db 명령어나 별도 테스트 환경에서 반드시 검증하세요.

Realtime 연결 제한

무료 플랜에서 동시 Realtime 연결 수가 제한됩니다. 대규모 실시간 기능은 연결 관리 전략을 세우고 Pro 플랜 이상을 고려하세요.

Edge Functions Cold Start

Edge Functions는 Deno 기반으로 Node.js 패키지 호환성이 제한적입니다. npm 패키지 중 일부는 동작하지 않을 수 있으니 esm.sh나 skypack CDN을 활용하세요.

PostgreSQL 지식 필요

SQL, 인덱싱, 트랜잭션, RLS 등 PostgreSQL 지식이 필요합니다. Firebase처럼 "쿼리 몰라도 됨" 수준은 아니므로 DB 기초 학습이 선행되어야 합니다.

🔗 관련 용어

📚 더 배우기