DevFinance
테크·9 min read

Supabase로 사이드프로젝트 백엔드 구축하기 2026 — 무료로 시작하는 완전 가이드

Supabase로 사이드 프로젝트의 백엔드를 무료로 구축하는 방법. PostgreSQL, 인증, RLS, 실시간 구독, Edge Functions, Next.js 연동까지 단계별로 정리했습니다.

Supabase — 사이드 프로젝트에 최적화된 백엔드

Supabase는 Firebase의 오픈소스 대안으로, PostgreSQL 기반의 인증·스토리지·실시간 구독·Edge Functions를 제공합니다. 직접 백엔드 서버를 구축하지 않고도 완전한 백엔드를 무료로 시작할 수 있어 사이드 프로젝트에 최적입니다.


무료 플랜 한도

항목무료 한도충분한가?
데이터베이스500MB초기 사이드 프로젝트에 넉넉
스토리지1GB이미지 수백 장 가능
대역폭5GB/월소규모 트래픽 충분
Edge Functions500,000 호출/월충분
인증 사용자50,000 MAU매우 충분
프로젝트 수2개사이드 프로젝트 여러 개 가능

무료 프로젝트는 7일 이상 활성 상태가 없으면 일시 정지됩니다. 월 1회 이상 접속으로 유지하세요.


프로젝트 생성 및 초기 설정

1단계: 프로젝트 생성

  1. supabase.com 가입
  2. "New Project" 클릭
  3. 리전 선택: Northeast Asia (Seoul) 이 가장 가깝습니다
  4. DB 비밀번호 설정 (반드시 안전하게 보관)
  5. 생성까지 약 1~2분 소요

2단계: 환경 변수 확인

Project Settings → API에서 확인:

  • NEXT_PUBLIC_SUPABASE_URL
  • NEXT_PUBLIC_SUPABASE_ANON_KEY
  • SUPABASE_SERVICE_ROLE_KEY (서버 사이드 전용, 절대 클라이언트에 노출 금지)

Next.js 연동

설치

npm install @supabase/supabase-js @supabase/ssr

클라이언트 설정

// src/lib/supabase/client.ts
import { createBrowserClient } from "@supabase/ssr";
import { Database } from "@/types/supabase";

export function createClient() {
  return createBrowserClient<Database>(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );
}
// src/lib/supabase/server.ts
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
import { Database } from "@/types/supabase";

export async function createClient() {
  const cookieStore = await cookies();
  return createServerClient<Database>(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll: () => cookieStore.getAll(),
        setAll: (cookiesToSet) => {
          cookiesToSet.forEach(({ name, value, options }) =>
            cookieStore.set(name, value, options)
          );
        },
      },
    }
  );
}

데이터베이스 설계와 RLS

테이블 생성 예시 (할 일 앱)

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

-- updated_at 자동 갱신 트리거
create or replace function update_updated_at()
returns trigger as $$
begin
  new.updated_at = now();
  return new;
end;
$$ language plpgsql;

create trigger todos_updated_at
  before update on todos
  for each row execute function update_updated_at();

-- 인덱스 추가 (user_id 기반 조회 빠르게)
create index todos_user_id_idx on todos(user_id);

Row Level Security (RLS) — 필수 설정

-- RLS 활성화 (반드시!)
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 없이 배포하면 누구나 모든 데이터에 접근할 수 있습니다. 반드시 활성화하세요.


CRUD 구현

TypeScript 타입 자동 생성

npx supabase gen types typescript --project-id your-project-id > src/types/supabase.ts

기본 CRUD

const supabase = createClient();

// 조회 (RLS 자동 적용)
const { data: todos, error } = await supabase
  .from("todos")
  .select("*")
  .order("created_at", { ascending: false });

// 생성
const { data, error } = await supabase
  .from("todos")
  .insert({ title: "Supabase 마스터하기" })
  .select()
  .single();

// 수정
const { error } = await supabase
  .from("todos")
  .update({ completed: true })
  .eq("id", todoId);

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

조건 조회

// 완료된 항목만
const { data } = await supabase
  .from("todos")
  .select("*")
  .eq("completed", true)
  .order("updated_at", { ascending: false })
  .limit(10);

// 검색
const { data } = await supabase
  .from("todos")
  .select("*")
  .ilike("title", `%${searchTerm}%`);

// 조인
const { data } = await supabase
  .from("posts")
  .select(`
    id,
    title,
    author:profiles(id, username, avatar_url)
  `);

인증 구현

이메일 + 소셜 로그인

// 이메일 회원가입
const { error } = await supabase.auth.signUp({
  email: "user@example.com",
  password: "secure-password",
  options: {
    data: { username: "devuser" }, // 사용자 메타데이터
  },
});

// 이메일 로그인
const { data, error } = await supabase.auth.signInWithPassword({
  email: "user@example.com",
  password: "secure-password",
});

// Google OAuth
const { error } = await supabase.auth.signInWithOAuth({
  provider: "google",
  options: {
    redirectTo: `${window.location.origin}/auth/callback`,
  },
});

// GitHub OAuth
const { error } = await supabase.auth.signInWithOAuth({
  provider: "github",
  options: {
    redirectTo: `${window.location.origin}/auth/callback`,
  },
});

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

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

인증 콜백 라우트 (Next.js App Router)

// app/auth/callback/route.ts
import { NextResponse } from "next/server";
import { createClient } from "@/lib/supabase/server";

export async function GET(request: Request) {
  const { searchParams, origin } = new URL(request.url);
  const code = searchParams.get("code");

  if (code) {
    const supabase = await createClient();
    await supabase.auth.exchangeCodeForSession(code);
  }

  return NextResponse.redirect(`${origin}/dashboard`);
}

실시간 구독

// 할 일 목록 실시간 동기화
const channel = supabase
  .channel("todos-channel")
  .on(
    "postgres_changes",
    {
      event: "*",
      schema: "public",
      table: "todos",
      filter: `user_id=eq.${userId}`,
    },
    (payload) => {
      if (payload.eventType === "INSERT") {
        setTodos((prev) => [payload.new as Todo, ...prev]);
      } else if (payload.eventType === "UPDATE") {
        setTodos((prev) =>
          prev.map((t) => (t.id === payload.new.id ? (payload.new as Todo) : t))
        );
      } else if (payload.eventType === "DELETE") {
        setTodos((prev) => prev.filter((t) => t.id !== payload.old.id));
      }
    }
  )
  .subscribe();

// 정리
return () => {
  supabase.removeChannel(channel);
};

Edge Functions

서버 사이드 로직을 Supabase 인프라에서 실행합니다.

// supabase/functions/send-notification/index.ts
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

serve(async (req) => {
  const supabase = createClient(
    Deno.env.get("SUPABASE_URL")!,
    Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
  );

  const { userId, message } = await req.json();

  // 데이터베이스에 알림 저장
  await supabase.from("notifications").insert({ user_id: userId, message });

  return new Response(JSON.stringify({ success: true }), {
    headers: { "Content-Type": "application/json" },
  });
});
# 배포
supabase functions deploy send-notification

# 호출
curl -X POST https://your-project.supabase.co/functions/v1/send-notification \
  -H "Authorization: Bearer YOUR_ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"userId": "uuid", "message": "안녕하세요!"}'

스토리지 (파일 업로드)

// 프로필 이미지 업로드
const { data, error } = await supabase.storage
  .from("avatars")
  .upload(`${user.id}/avatar.jpg`, file, {
    upsert: true,
    contentType: "image/jpeg",
  });

// 공개 URL 가져오기
const { data: { publicUrl } } = supabase.storage
  .from("avatars")
  .getPublicUrl(`${user.id}/avatar.jpg`);

사이드 프로젝트 아키텍처 예시

Next.js (Vercel)
    ↕ supabase-js
Supabase
    ├── PostgreSQL (데이터베이스)
    ├── Auth (인증)
    ├── Storage (파일)
    ├── Realtime (실시간 동기화)
    └── Edge Functions (서버 로직)

이 스택으로 만들 수 있는 사이드 프로젝트:

  • 북마크·링크 관리 앱
  • 팀 투표·설문 도구
  • 습관 트래커
  • 개발자 TIL 블로그
  • 단순 SaaS (대기 목록, 피드백 수집)
  • 실시간 채팅 앱

실전 체크리스트

배포 전 필수 확인:
□ 모든 테이블에 RLS 활성화 되어 있는가?
□ anon key가 서버 전용 로직에 사용되지 않는가?
□ service_role_key가 클라이언트에 노출되지 않는가?
□ TypeScript 타입이 최신 스키마와 동기화되어 있는가?
□ Edge Functions 환경 변수가 설정되어 있는가?
□ 스토리지 버킷 접근 정책이 올바르게 설정되어 있는가?

관련 글: Vercel 무료 배포 완전 가이드 · Next.js SSG 완전 가이드 · 개발자 포트폴리오 만들기

DF

DevFinance 편집팀

현직 소프트웨어 엔지니어가 운영합니다. IT 업계 종사자에게 필요한 재테크·기술 정보를 공식 데이터와 실무 경험을 바탕으로 정리합니다.

더 알아보기