Choorai
Lv.3 BaaS

Supabase 시작하기

Backend-as-a-Service로 별도 백엔드 서버 없이 PostgreSQL 데이터베이스와 인증을 사용할 수 있습니다.

Supabase란?

  • PostgreSQL 기반 관리형 데이터베이스
  • Auth - 이메일/소셜 로그인 내장
  • Row Level Security - 데이터 접근 권한 제어
  • 실시간 구독 - 데이터 변경 시 자동 알림
  • 무료 티어 - 50,000 MAU, 500MB 스토리지

1Supabase 프로젝트 생성

  1. supabase.com 에서 가입 (GitHub 로그인 추천)
  2. New project 클릭
  3. 프로젝트 이름 입력
  4. 데이터베이스 비밀번호 설정 (안전한 곳에 저장!)
  5. 리전 선택: Northeast Asia (Seoul) 추천
  6. Create new project 클릭

정보

프로젝트 생성에 1-2분 정도 소요됩니다. 생성 완료 후 대시보드에서 API 키를 확인할 수 있습니다.

2테이블 생성 (SQL Editor)

대시보드에서 SQL Editor를 열고 아래 스키마를 실행하세요.

SQL Editor
-- todos 테이블 생성
create table todos (
  id uuid default gen_random_uuid() primary key,
  user_id uuid references auth.users(id) on delete cascade,
  title text not null,
  completed boolean default false,
  created_at timestamptz default now()
);

-- RLS (Row Level Security) 활성화
alter table todos enable row level security;

-- 정책: 자신의 데이터만 조회 가능
create policy "Users can view own todos"
  on todos for select
  using (auth.uid() = user_id);

-- 정책: 자신의 데이터만 추가 가능
create policy "Users can insert own todos"
  on todos for insert
  with check (auth.uid() = user_id);

-- 정책: 자신의 데이터만 수정 가능
create policy "Users can update own todos"
  on todos for update
  using (auth.uid() = user_id);

-- 정책: 자신의 데이터만 삭제 가능
create policy "Users can delete own todos"
  on todos for delete
  using (auth.uid() = user_id);

RLS가 중요한 이유

RLS 없이는 모든 사용자가 모든 데이터에 접근할 수 있습니다. auth.uid()는 현재 로그인한 사용자의 ID를 반환합니다.

3클라이언트 설정 (React)

의존성 설치

터미널
npm install @supabase/supabase-js @tanstack/react-query

환경 변수 설정

.env.local
# .env.local
VITE_SUPABASE_URL=https://your-project-id.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key

Supabase 대시보드 → SettingsAPI에서 확인하세요.

Supabase 클라이언트

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

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;

export const supabase = createClient(supabaseUrl, supabaseAnonKey);

4CRUD 예제 (React Query + Supabase)

타입 정의

src/types/todo.ts
// src/types/todo.ts
export interface Todo {
  id: string;
  user_id: string;
  title: string;
  completed: boolean;
  created_at: string;
}

export type CreateTodoInput = Pick<Todo, 'title'>;
export type UpdateTodoInput = Partial<Pick<Todo, 'title' | 'completed'>>;

Hooks (TanStack Query)

src/hooks/useTodos.ts
// src/hooks/useTodos.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { supabase } from '../lib/supabase';
import type { Todo, CreateTodoInput, UpdateTodoInput } from '../types/todo';

// 목록 조회
export function useTodos() {
  return useQuery({
    queryKey: ['todos'],
    queryFn: async () => {
      const { data, error } = await supabase
        .from('todos')
        .select('*')
        .order('created_at', { ascending: false });

      if (error) throw error;
      return data as Todo[];
    },
  });
}

// 생성
export function useCreateTodo() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async (input: CreateTodoInput) => {
      const { data: { user } } = await supabase.auth.getUser();
      if (!user) throw new Error('Not authenticated');

      const { data, error } = await supabase
        .from('todos')
        .insert({ ...input, user_id: user.id })
        .select()
        .single();

      if (error) throw error;
      return data as Todo;
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['todos'] });
    },
  });
}

// 수정
export function useUpdateTodo() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async ({ id, ...input }: UpdateTodoInput & { id: string }) => {
      const { data, error } = await supabase
        .from('todos')
        .update(input)
        .eq('id', id)
        .select()
        .single();

      if (error) throw error;
      return data as Todo;
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['todos'] });
    },
  });
}

// 삭제
export function useDeleteTodo() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async (id: string) => {
      const { error } = await supabase
        .from('todos')
        .delete()
        .eq('id', id);

      if (error) throw error;
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['todos'] });
    },
  });
}

5인증 예제 (선택)

Supabase Auth를 사용하면 이메일/비밀번호 로그인을 쉽게 구현할 수 있습니다.

src/hooks/useAuth.ts
// src/hooks/useAuth.ts
import { useState, useEffect } from 'react';
import { supabase } from '../lib/supabase';
import type { User } from '@supabase/supabase-js';

export function useAuth() {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // 현재 세션 확인
    supabase.auth.getSession().then(({ data: { session } }) => {
      setUser(session?.user ?? null);
      setLoading(false);
    });

    // 인증 상태 변경 감지
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      (_event, session) => {
        setUser(session?.user ?? null);
      }
    );

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

  // 회원가입
  const signUp = async (email: string, password: string) => {
    const { error } = await supabase.auth.signUp({ email, password });
    if (error) throw error;
  };

  // 로그인
  const signIn = async (email: string, password: string) => {
    const { error } = await supabase.auth.signInWithPassword({ email, password });
    if (error) throw error;
  };

  // 로그아웃
  const signOut = async () => {
    const { error } = await supabase.auth.signOut();
    if (error) throw error;
  };

  return { user, loading, signUp, signIn, signOut };
}

소셜 로그인

GitHub, Google, Discord 등 소셜 로그인도 지원합니다. Supabase 대시보드 → AuthenticationProviders에서 설정하세요.

언제 Supabase를 사용하면 좋을까요?

✅ 추천하는 경우

  • 빠르게 MVP 만들 때
  • 백엔드 개발 없이 프론트만 집중하고 싶을 때
  • 실시간 기능이 필요할 때
  • 인증 + DB를 한 번에 해결하고 싶을 때

⚠️ 고려가 필요한 경우

  • 복잡한 비즈니스 로직이 있을 때
  • 외부 API 연동이 많을 때
  • 커스텀 백엔드 로직이 필수일 때
  • 데이터 구조가 자주 바뀔 때

다음 단계

마지막 업데이트: 2026년 2월 22일 · 버전: v0.0.1

피드백 보내기

입력한 내용으로 새 이슈 페이지를 엽니다.

GitHub 이슈로 보내기