콘텐츠로 건너뛰기
돌아가기
초보자를 위한 Supabase 시작 가이드

초보자를 위한 Supabase 시작 가이드

게시일:  at  10:13 오전

1. 왜 Supabase인가?

백엔드 개발은 참으로 복잡하다. 서버 설정하고, 데이터베이스 구축하고, 인증 시스템 만들고… 프론트엔드 개발자에게는 너무 벅찬 일이다.

Firebase가 이런 문제를 해결해줬지만, 아쉬운 점이 있었다. 가격이 비싸고, 벤더 락인 문제가 있다. 그리고 무엇보다 SQL을 못 쓴다는 게 아쉬웠다.

이럴 때 Supabase가 등장했다. 오픈소스이면서 PostgreSQL을 사용하고, Firebase만큼 쉽다. 참으로 훌륭하지 않은가?

2. Supabase란?

2.1 핵심 개념

Supabase는 오픈소스 Firebase 대안이다. PostgreSQL 데이터베이스를 기반으로 만들어졌다.

다음과 같은 기능을 제공한다:

  • PostgreSQL 데이터베이스
  • 인증 시스템 (Authentication)
  • 실시간 구독 (Realtime)
  • 파일 저장소 (Storage)
  • Edge Functions (서버리스 함수)

2.2 Firebase vs Supabase

항목FirebaseSupabase
데이터베이스NoSQL (Firestore)PostgreSQL (SQL)
가격비쌈저렴 (무료 플랜도 좋음)
오픈소스
SQL 지원
실시간

SQL을 쓸 수 있다는 게 참으로 큰 장점이다. 복잡한 쿼리도 자유롭게 작성할 수 있다.

3. 프로젝트 생성하기

3.1 회원가입

먼저 supabase.com에 접속한다.

GitHub 계정으로 간단히 로그인할 수 있다. 이메일로도 가능하다.

3.2 새 프로젝트 만들기

대시보드에서 “New Project” 버튼을 클릭한다.

다음 정보를 입력한다:

  • Project name: 원하는 이름
  • Database password: 강력한 비밀번호 (꼭 저장해두자!)
  • Region: 가까운 지역 선택 (서울은 없고 도쿄가 제일 가깝다)

무료 플랜도 충분히 좋다.

  • 500MB 데이터베이스
  • 1GB 파일 저장소
  • 50,000명 월간 활성 사용자

프로젝트 생성에는 1-2분 정도 걸린다.

3.3 API 키 확인하기

프로젝트가 생성되면 Settings > API로 이동한다.

두 개의 키가 있다:

  • anon key: 클라이언트에서 사용 (공개 가능)
  • service_role key: 서버에서만 사용 (절대 노출 금지!)

이 키들을 복사해서 .env 파일에 저장한다.

NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key

간단하지 않은가?

4. 데이터베이스 테이블 만들기

4.1 SQL Editor 사용하기

대시보드에서 SQL Editor를 열어보자.

첫 번째 테이블을 만들어보자. 간단한 할 일 관리 앱의 테이블이다.

-- todos 테이블 생성
create table todos (
  id bigint generated by default as identity primary key,
  user_id uuid references auth.users not null,
  task text not null,
  is_complete boolean default false,
  created_at timestamp with time zone default timezone('utc'::text, now()) not null
);

“Run” 버튼을 누르면 테이블이 생성된다.

4.2 테이블 구조 설명

위 SQL을 하나씩 살펴보자:

  • id: 자동 증가하는 기본 키
  • user_id: 사용자 ID (auth.users 테이블 참조)
  • task: 할 일 내용
  • is_complete: 완료 여부
  • created_at: 생성 시간 (UTC 기준)

PostgreSQL의 강력함이 느껴진다. 외래 키도 쉽게 설정할 수 있다.

4.3 데이터 추가하기

이제 데이터를 넣어보자.

Table Editor로 가면 GUI로 데이터를 추가할 수 있다. 물론 SQL로도 가능하다.

-- 테스트 데이터 추가
insert into todos (user_id, task)
values
  (auth.uid(), 'Supabase 튜토리얼 완료하기'),
  (auth.uid(), '블로그 글 작성하기');

참으로 쉽다!

5. API로 데이터 다루기

5.1 프로젝트 설정

Next.js 프로젝트를 만들어보자.

npx create-next-app@latest my-supabase-app
cd my-supabase-app

Supabase 클라이언트를 설치한다.

npm install @supabase/supabase-js

5.2 Supabase 클라이언트 초기화

lib/supabase.js 파일을 만든다.

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);

이게 끝이다. 참으로 간단하지 않은가?

5.3 데이터 조회하기 (SELECT)

모든 할 일을 가져오는 코드다.

import { supabase } from "@/lib/supabase";

// 모든 todos 가져오기
const { data, error } = await supabase.from("todos").select("*");

if (error) {
  console.error("Error:", error);
} else {
  console.log("Todos:", data);
}

SQL을 몰라도 쉽게 쓸 수 있다. 물론 SQL로도 작성 가능하다.

5.4 데이터 추가하기 (INSERT)

새 할 일을 추가해보자.

// 새 todo 추가
const { data, error } = await supabase
  .from("todos")
  .insert([{ task: "Supabase 마스터하기", is_complete: false }])
  .select();

console.log("New todo:", data);

반환된 data에 새로 추가된 행이 들어있다.

5.5 데이터 수정하기 (UPDATE)

할 일을 완료 처리해보자.

// todo 완료 처리
const { data, error } = await supabase
  .from("todos")
  .update({ is_complete: true })
  .eq("id", 1)
  .select();

.eq()는 where 조건이다. id가 1인 행만 업데이트된다.

5.6 데이터 삭제하기 (DELETE)

이제 삭제도 해보자.

// todo 삭제
const { error } = await supabase.from("todos").delete().eq("id", 1);

CRUD가 이렇게 간단하다니!

6. Authentication 구현하기

6.1 이메일 회원가입

Supabase는 인증 시스템을 내장하고 있다. 직접 구현하면 며칠 걸릴 작업이 몇 줄로 끝난다.

// 회원가입
const { data, error } = await supabase.auth.signUp({
  email: "[email protected]",
  password: "password123",
});

회원가입하면 이메일로 인증 링크가 간다. (물론 개발 중엔 이메일 인증을 끌 수도 있다.)

6.2 로그인

로그인도 똑같이 간단하다.

// 로그인
const { data, error } = await supabase.auth.signInWithPassword({
  email: "[email protected]",
  password: "password123",
});

// 현재 사용자 정보
const {
  data: { user },
} = await supabase.auth.getUser();
console.log("Logged in:", user.email);

JWT 토큰이 자동으로 쿠키에 저장된다. 새로고침해도 로그인 상태가 유지된다.

6.3 소셜 로그인

Google, GitHub 로그인도 지원한다.

먼저 대시보드에서 Provider를 활성화한다. Authentication > Providers > Google 선택

// Google 로그인
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: "google",
});

한 줄이면 끝이다. 참으로 놀랍지 않은가?

6.4 로그아웃

로그아웃도 간단하다.

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

7. Row-Level Security (RLS)

7.1 RLS가 뭔가?

RLS는 행 단위 보안이다. 사용자가 자신의 데이터만 볼 수 있게 제한한다.

예를 들어 보자. A 사용자는 자기 할 일만 봐야 한다. B 사용자의 할 일을 보면 안 된다.

RLS가 바로 이걸 해결한다.

7.2 RLS 활성화하기

Table Editor에서 테이블을 선택한다. 오른쪽 상단의 “RLS” 버튼을 켠다.

처음엔 아무것도 안 보인다. (모든 접근이 차단됨) 이제 정책(Policy)을 만들어야 한다.

7.3 SELECT 정책 만들기

사용자가 자신의 데이터만 조회하게 하자.

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

auth.uid()는 현재 로그인한 사용자 ID다. user_id와 같은 행만 조회할 수 있다.

7.4 INSERT 정책 만들기

자신의 데이터만 추가하게 하자.

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

7.5 UPDATE/DELETE 정책

수정과 삭제도 똑같이 만든다.

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

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

이제 완벽하게 보안이 적용됐다!

8. React/Next.js 컴포넌트 만들기

8.1 로그인 폼

실제로 사용할 로그인 컴포넌트를 만들어보자.

// components/LoginForm.jsx
import { useState } from "react";
import { supabase } from "@/lib/supabase";

export default function LoginForm() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [loading, setLoading] = useState(false);

  const handleLogin = async e => {
    e.preventDefault();
    setLoading(true);

    const { error } = await supabase.auth.signInWithPassword({
      email,
      password,
    });

    if (error) {
      alert("로그인 실패: " + error.message);
    } else {
      alert("로그인 성공!");
    }

    setLoading(false);
  };

  return (
    <form onSubmit={handleLogin}>
      <input
        type="email"
        placeholder="이메일"
        value={email}
        onChange={e => setEmail(e.target.value)}
        required
      />
      <input
        type="password"
        placeholder="비밀번호"
        value={password}
        onChange={e => setPassword(e.target.value)}
        required
      />
      <button type="submit" disabled={loading}>
        {loading ? "로그인 중..." : "로그인"}
      </button>
    </form>
  );
}

8.2 Todos 목록

할 일 목록을 보여주는 컴포넌트다.

{% raw %}

// components/TodoList.jsx
import { useState, useEffect } from "react";
import { supabase } from "@/lib/supabase";

export default function TodoList() {
  const [todos, setTodos] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchTodos();
  }, []);

  const fetchTodos = async () => {
    const { data, error } = await supabase
      .from("todos")
      .select("*")
      .order("created_at", { ascending: false });

    if (error) {
      console.error("Error:", error);
    } else {
      setTodos(data);
    }
    setLoading(false);
  };

  const toggleComplete = async (id, is_complete) => {
    await supabase
      .from("todos")
      .update({ is_complete: !is_complete })
      .eq("id", id);

    fetchTodos(); // 목록 새로고침
  };

  if (loading) return <p>로딩 중...</p>;

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          <input
            type="checkbox"
            checked={todo.is_complete}
            onChange={() => toggleComplete(todo.id, todo.is_complete)}
          />
          <span
            style={{
              textDecoration: todo.is_complete ? "line-through" : "none",
            }}
          >
            {todo.task}
          </span>
        </li>
      ))}
    </ul>
  );
}

{% endraw %}

실시간으로 체크박스를 토글할 수 있다!

8.3 실시간 구독

Supabase의 실시간 기능을 써보자. 다른 사용자가 데이터를 추가하면 자동으로 화면이 업데이트된다.

useEffect(() => {
  fetchTodos();

  // 실시간 구독
  const channel = supabase
    .channel("todos-changes")
    .on(
      "postgres_changes",
      { event: "*", schema: "public", table: "todos" },
      payload => {
        console.log("Change received!", payload);
        fetchTodos(); // 목록 새로고침
      }
    )
    .subscribe();

  // 컴포넌트 언마운트 시 구독 해제
  return () => {
    supabase.removeChannel(channel);
  };
}, []);

이제 다른 탭에서 데이터를 추가하면 자동으로 반영된다. 참으로 멋지지 않은가?

9. 주의사항 및 팁

9.1 자주 하는 실수

  • RLS 설정 안 함: 가장 흔한 실수다. 반드시 RLS를 켜야 한다.
  • Service Role 키 노출: 클라이언트에서 절대 사용하면 안 된다.
  • 인덱스 안 만듦: 쿼리가 느리면 인덱스를 추가하자.
  • 에러 처리 안 함: 항상 error를 체크해야 한다.

9.2 최적화 팁

  • 쿼리 최적화: .select()에 필요한 컬럼만 명시하자.
  • 캐싱 활용: React Query나 SWR을 써서 불필요한 요청을 줄이자.
  • 실시간 구독 제한: 모든 테이블에 실시간을 켜지 말자. (비용과 성능 문제)
  • Connection Pooling: 많은 요청이 있다면 Supavisor를 써보자.

9.3 보안 체크리스트

  • ✅ RLS 정책이 모든 테이블에 적용됐는가?
  • ✅ Service Role 키가 환경변수에 안전하게 저장됐는가?
  • ✅ 이메일 인증이 활성화됐는가?
  • ✅ 비밀번호 정책이 충분히 강력한가?
  • ✅ CORS 설정이 올바른가?

10. 정리

Supabase로 백엔드를 만드는 건 참으로 쉬웠다.

핵심을 정리하면:

  • PostgreSQL 기반이라 SQL을 자유롭게 쓸 수 있다
  • 인증 시스템이 내장돼 있어 별도 구현이 필요 없다
  • RLS로 데이터 보안을 간단히 적용할 수 있다
  • 실시간 구독으로 collaborative 앱을 쉽게 만든다
  • 무료 플랜도 충분히 좋아서 부담 없이 시작할 수 있다

Firebase를 쓰다가 넘어온 사람이라면 더욱 만족할 것이다. SQL의 강력함과 오픈소스의 자유로움을 동시에 누릴 수 있다.

처음엔 낯설 수 있지만, 한번 익숙해지면 돌아갈 수 없다. (경험상 그렇다… ㅎㅎ)

이제 여러분도 Supabase로 멋진 앱을 만들어보자!