logoRawon_Log
홈블로그소개

Built with Next.js, Bun, Tailwind CSS and Shadcn/UI

DB

Supabase 가이드

Rawon
2025년 7월 29일
목차
Supabase 완벽 가이드: Firebase 대안으로 백엔드 없이 데이터베이스 구축하기
1. Supabase란 무엇인가?
2. Firebase와 Supabase 비교
3. Firebase가 아닌 Supabase를 선택한 이유
4. Supabase 활용 방법
4-1. DB 생성
4-2. 테이블 생성 + 제약조건 설정 + record 수동 입력
테이블 생성하기:
데이터베이스 기본 개념 설명:
예시: 간단한 블로그 데이터베이스 구축
레코드 수동 입력:
4-3. React 프로젝트에 Supabase 적용
설정:
기본 CRUD 작업:
4-4. Supabase 이용, RESTful API 작성(Node.js+Express)
4-5. 만들어진 API 호출(React, Axios 활용)
5. 데이터가 조회되지 않는다!
5-1. RLS란?
5-2. RLS 설정 방법
5-3. SELECT / INSERT / DELETE / UPDATE 각각 설정 방법
SELECT 정책 (데이터 읽기)
INSERT 정책 (데이터 생성)
UPDATE 정책 (데이터 수정)
DELETE 정책 (데이터 삭제)
RLS 정책 디버깅 팁:
6. 추가 기능: Supabase의 실시간(Realtime) 기능
실시간 기능 활성화:
React에서 실시간 구독 사용:
7. 결론
참고 자료

목차

Supabase 완벽 가이드: Firebase 대안으로 백엔드 없이 데이터베이스 구축하기
1. Supabase란 무엇인가?
2. Firebase와 Supabase 비교
3. Firebase가 아닌 Supabase를 선택한 이유
4. Supabase 활용 방법
4-1. DB 생성
4-2. 테이블 생성 + 제약조건 설정 + record 수동 입력
테이블 생성하기:
데이터베이스 기본 개념 설명:
예시: 간단한 블로그 데이터베이스 구축
레코드 수동 입력:
4-3. React 프로젝트에 Supabase 적용
설정:
기본 CRUD 작업:
4-4. Supabase 이용, RESTful API 작성(Node.js+Express)
4-5. 만들어진 API 호출(React, Axios 활용)
5. 데이터가 조회되지 않는다!
5-1. RLS란?
5-2. RLS 설정 방법
5-3. SELECT / INSERT / DELETE / UPDATE 각각 설정 방법
SELECT 정책 (데이터 읽기)
INSERT 정책 (데이터 생성)
UPDATE 정책 (데이터 수정)
DELETE 정책 (데이터 삭제)
RLS 정책 디버깅 팁:
6. 추가 기능: Supabase의 실시간(Realtime) 기능
실시간 기능 활성화:
React에서 실시간 구독 사용:
7. 결론
참고 자료

Supabase 완벽 가이드: Firebase 대안으로 백엔드 없이 데이터베이스 구축하기

1. Supabase란 무엇인가?

Supabase는 오픈 소스 Firebase 대안으로, 웹 및 모바일 애플리케이션 개발에 필요한 백엔드 서비스를 제공합니다. Supabase는 다음과 같은 핵심 기능을 제공합니다:

  • Postgres 데이터베이스: 세계에서 가장 신뢰받는 관계형 데이터베이스
  • 인증 시스템: 사용자 회원가입 및 로그인 관리
  • 실시간 구독: 데이터베이스 변경사항을 실시간으로 수신
  • 자동 생성 API: 데이터베이스에 즉시 접근할 수 있는 API
  • 스토리지: 파일 저장 및 관리
  • 벡터 임베딩: AI 애플리케이션을 위한 벡터 저장소
  • Edge Functions: 서버리스 함수 실행

Supabase를 사용하면 백엔드 개발 없이도 강력한 애플리케이션을 "주말에 구축하고 수백만 명에게 확장"할 수 있습니다.

2. Firebase와 Supabase 비교

특징FirebaseSupabase
데이터베이스NoSQL(Firestore)PostgreSQL(관계형)
오픈소스❌✅
SQL 지원❌✅
마이그레이션 용이성어려움 (독점 시스템)쉬움 (표준 Postgres)
실시간 기능✅✅
인증✅✅
파일 스토리지✅✅
서버리스 함수Cloud FunctionsEdge Functions
가격 정책사용량 기반, 높은 확장 비용예측 가능한 요금제
로컬 개발제한적완전한 로컬 개발 지원

3. Firebase가 아닌 Supabase를 선택한 이유

  1. 관계형 데이터베이스의 강점: PostgreSQL은 복잡한 쿼리, 관계 및 제약 조건을 지원합니다.
  2. 오픈 소스: 코드가 공개되어 있어 커뮤니티 기여와 투명성이 보장됩니다.
  3. SQL의 친숙함: 많은 개발자가 이미 SQL에 익숙합니다.
  4. 마이그레이션 자유: 표준 PostgreSQL을 사용하므로 필요할 때 다른 플랫폼으로 쉽게 이전할 수 있습니다.
  5. 비용 효율성: 예측 가능한 가격 모델로 확장 시 비용 폭증 위험이 적습니다.
  6. 로컬 개발 환경: 완전한 로컬 개발 환경을 제공합니다.
  7. Row Level Security (RLS): 강력한 보안 정책을 데이터베이스 수준에서 구현할 수 있습니다.

4. Supabase 활용 방법

4-1. DB 생성

Supabase에서 새로운 프로젝트를 만드는 방법:

  1. Supabase에 접속하여 계정 생성 또는 로그인
  2. 대시보드에서 "New Project" 클릭
  3. 프로젝트 이름, 비밀번호, 지역(Region) 설정
  4. "Create new project" 클릭하고 설정이 완료될 때까지 대기 (약 1-2분 소요)

알아두면 좋은 점: Supabase는 각 프로젝트마다 완전한 PostgreSQL 데이터베이스를 제공합니다. 이는 여러분이 완전한 데이터베이스 관리 시스템에 접근할 수 있음을 의미합니다.

4-2. 테이블 생성 + 제약조건 설정 + record 수동 입력

테이블 생성하기:

  1. Supabase 대시보드에서 "Table Editor" 탭으로 이동
  2. "Create a new table" 클릭
  3. 테이블 이름 입력 (예: users)
  4. 열(Column) 정의:
    • 이름 (예: id, name, email 등)
    • 데이터 타입 선택 (예: uuid, text, varchar, integer 등)
    • 기본값 설정 (필요시)

데이터베이스 기본 개념 설명:

Primary Key (기본 키):

  • 테이블의 각 행을 고유하게 식별하는 열입니다.
  • 중복된 값을 가질 수 없으며, NULL 값도 허용되지 않습니다.
  • 예시: 사용자 테이블의 id 열

Foreign Key (외래 키):

  • 다른 테이블의 Primary Key를 참조하는 열입니다.
  • 테이블 간의 관계를 설정할 때 사용합니다.
  • 예시: comments 테이블의 user_id 열이 users 테이블의 id를 참조

제약 조건 설정하기:

  1. 테이블 생성 시 "Constraints" 섹션에서 설정 가능
  2. Primary Key 설정: 열 옆의 "Primary Key" 체크박스 선택
  3. Foreign Key 설정: "Foreign Key Constraints" 섹션에서 "Add Foreign Key"를 클릭하고 참조할 테이블과 열 선택
  4. Unique 제약 조건: 중복 값을 허용하지 않는 설정
  5. Not Null 제약 조건: NULL 값을 허용하지 않는 설정

예시: 간단한 블로그 데이터베이스 구축

Users 테이블 생성:

sql
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  name TEXT NOT NULL,
  email TEXT UNIQUE NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

Posts 테이블 생성 (외래 키 포함):

sql
CREATE TABLE posts (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  title TEXT NOT NULL,
  content TEXT,
  user_id UUID REFERENCES users(id),
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

레코드 수동 입력:

  1. 테이블을 선택하고 "Insert" 탭 클릭
  2. 각 열에 대한 값 입력
  3. "Save" 버튼 클릭

또는 SQL 에디터에서 INSERT 문 사용:

sql
INSERT INTO users (name, email)
VALUES ('홍길동', 'hong@example.com');

INSERT INTO posts (title, content, user_id)
VALUES (
  '첫 번째 게시물',
  '안녕하세요, 이것은 제 첫 게시물입니다.',
  '여기에 user_id 값 입력'
);

4-3. React 프로젝트에 Supabase 적용

설정:

  1. React 프로젝트에 Supabase 클라이언트 설치:
bash
npm install @supabase/supabase-js
  1. Supabase 클라이언트 초기화:
javascript
// src/supabase.js
import { createClient } from '@supabase/supabase-js';

const supabaseUrl = '<https://your-project-url.supabase.co>';
const supabaseAnonKey = 'your-anon-key';

export const supabase = createClient(supabaseUrl, supabaseAnonKey);

기본 CRUD 작업:

데이터 조회 (Read):

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

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchUsers() {
      try {
        const { data, error } = await supabase
          .from('users')
          .select('*');

        if (error) throw error;
        setUsers(data || []);
      } catch (error) {
        console.error('Error fetching users:', error.message);
      } finally {
        setLoading(false);
      }
    }

    fetchUsers();
  }, []);

  if (loading) return <div>데이터를 불러오는 중...</div>;

  return (
    <div>
      <h2>사용자 목록</h2>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name} ({user.email})</li>
        ))}
      </ul>
    </div>
  );
}

데이터 생성 (Create):

javascript
import { supabase } from './supabase';
import { useState } from 'react';

function CreateUser() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [loading, setLoading] = useState(false);
  const [message, setMessage] = useState('');

  async function handleSubmit(e) {
    e.preventDefault();

    if (!name || !email) {
      setMessage('이름과 이메일을 모두 입력해주세요.');
      return;
    }

    setLoading(true);

    try {
      const { data, error } = await supabase
        .from('users')
        .insert([{ name, email }])
        .select();

      if (error) throw error;

      setMessage('사용자가 성공적으로 추가되었습니다!');
      setName('');
      setEmail('');
    } catch (error) {
      setMessage(`오류: ${error.message}`);
    } finally {
      setLoading(false);
    }
  }

  return (
    <div>
      <h2>사용자 추가</h2>
      {message && <p>{message}</p>}

      <form onSubmit={handleSubmit}>
        <div>
          <label>이름:</label>
          <input
            type="text"
            value={name}
            onChange={e => setName(e.target.value)}
          />
        </div>

        <div>
          <label>이메일:</label>
          <input
            type="email"
            value={email}
            onChange={e => setEmail(e.target.value)}
          />
        </div>

        <button type="submit" disabled={loading}>
          {loading ? '처리 중...' : '사용자 추가'}
        </button>
      </form>
    </div>
  );
}

데이터 업데이트 (Update):

javascript
const { data, error } = await supabase
  .from('users')
  .update({ name: '새 이름' })
  .eq('id', userId);

데이터 삭제 (Delete):

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

4-4. Supabase 이용, RESTful API 작성(Node.js+Express)

Node.js와 Express로 Supabase를 사용한 API 서버를 구축하는 방법:

  1. 필요한 패키지 설치:
bash
npm install express @supabase/supabase-js cors dotenv
  1. 기본 서버 설정:
javascript
// server.js
const express = require('express');
const { createClient } = require('@supabase/supabase-js');
const cors = require('cors');
require('dotenv').config();

const app = express();
app.use(cors());
app.use(express.json());

const supabaseUrl = process.env.SUPABASE_URL;
const supabaseServiceKey = process.env.SUPABASE_SERVICE_KEY; // 서비스 롤 키 사용
const supabase = createClient(supabaseUrl, supabaseServiceKey);

// 사용자 목록 조회
app.get('/api/users', async (req, res) => {
  try {
    const { data, error } = await supabase
      .from('users')
      .select('*');

    if (error) throw error;

    res.json(data);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// 특정 사용자 조회
app.get('/api/users/:id', async (req, res) => {
  try {
    const { id } = req.params;
    const { data, error } = await supabase
      .from('users')
      .select('*')
      .eq('id', id)
      .single();

    if (error) throw error;

    if (!data) {
      return res.status(404).json({ error: '사용자를 찾을 수 없습니다.' });
    }

    res.json(data);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// 사용자 생성
app.post('/api/users', async (req, res) => {
  try {
    const { name, email } = req.body;

    if (!name || !email) {
      return res.status(400).json({ error: '이름과 이메일은 필수 항목입니다.' });
    }

    const { data, error } = await supabase
      .from('users')
      .insert([{ name, email }])
      .select();

    if (error) throw error;

    res.status(201).json(data[0]);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// 사용자 업데이트
app.put('/api/users/:id', async (req, res) => {
  try {
    const { id } = req.params;
    const { name, email } = req.body;

    const { data, error } = await supabase
      .from('users')
      .update({ name, email })
      .eq('id', id)
      .select();

    if (error) throw error;

    if (data.length === 0) {
      return res.status(404).json({ error: '사용자를 찾을 수 없습니다.' });
    }

    res.json(data[0]);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// 사용자 삭제
app.delete('/api/users/:id', async (req, res) => {
  try {
    const { id } = req.params;

    const { data, error } = await supabase
      .from('users')
      .delete()
      .eq('id', id)
      .select();

    if (error) throw error;

    if (data.length === 0) {
      return res.status(404).json({ error: '사용자를 찾을 수 없습니다.' });
    }

    res.json({ message: '사용자가 성공적으로 삭제되었습니다.' });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
  console.log(`서버가 <http://localhost>:${PORT} 에서 실행 중입니다.`);
});

중요: 서버에서는 서비스 롤 키를 사용합니다. 이 키는 RLS 정책을 우회할 수 있으므로 환경 변수로 안전하게 보관하고, 클라이언트 코드에는 절대 노출하지 마세요.

4-5. 만들어진 API 호출(React, Axios 활용)

React와 Axios를 사용하여 Express API를 호출하는 방법:

  1. Axios 설치:
bash
npm install axios
  1. API 호출 서비스 작성:
javascript
// src/services/api.js
import axios from 'axios';

const API_URL = '<http://localhost:3001/api>';

export const userService = {
  // 사용자 목록 조회
  getUsers: async () => {
    try {
      const response = await axios.get(`${API_URL}/users`);
      return response.data;
    } catch (error) {
      throw error.response?.data || error.message;
    }
  },

  // 특정 사용자 조회
  getUser: async (id) => {
    try {
      const response = await axios.get(`${API_URL}/users/${id}`);
      return response.data;
    } catch (error) {
      throw error.response?.data || error.message;
    }
  },

  // 사용자 생성
  createUser: async (userData) => {
    try {
      const response = await axios.post(`${API_URL}/users`, userData);
      return response.data;
    } catch (error) {
      throw error.response?.data || error.message;
    }
  },

  // 사용자 업데이트
  updateUser: async (id, userData) => {
    try {
      const response = await axios.put(`${API_URL}/users/${id}`, userData);
      return response.data;
    } catch (error) {
      throw error.response?.data || error.message;
    }
  },

  // 사용자 삭제
  deleteUser: async (id) => {
    try {
      const response = await axios.delete(`${API_URL}/users/${id}`);
      return response.data;
    } catch (error) {
      throw error.response?.data || error.message;
    }
  }
};
  1. React 컴포넌트에서 API 호출:
javascript
import { useState, useEffect } from 'react';
import { userService } from '../services/api';

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function loadUsers() {
      try {
        const data = await userService.getUsers();
        setUsers(data);
      } catch (err) {
        setError(err.error || '사용자 데이터를 불러오는 중 오류가 발생했습니다.');
      } finally {
        setLoading(false);
      }
    }

    loadUsers();
  }, []);

  async function handleDeleteUser(id) {
    if (window.confirm('정말 이 사용자를 삭제하시겠습니까?')) {
      try {
        await userService.deleteUser(id);
        // 삭제 후 목록 업데이트
        setUsers(users.filter(user => user.id !== id));
      } catch (err) {
        alert(err.error || '사용자를 삭제하는 중 오류가 발생했습니다.');
      }
    }
  }

  if (loading) return <div>로딩 중...</div>;
  if (error) return <div>오류: {error}</div>;

  return (
    <div>
      <h2>사용자 목록</h2>
      {users.length === 0 ? (
        <p>사용자가 없습니다.</p>
      ) : (
        <ul>
          {users.map(user => (
            <li key={user.id}>
              {user.name} ({user.email})
              <button onClick={() => handleDeleteUser(user.id)}>삭제</button>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

5. 데이터가 조회되지 않는다!

Supabase에서 데이터를 조회할 수 없는 가장 흔한 이유는 Row Level Security(RLS) 설정 때문입니다.

5-1. RLS란?

Row Level Security(RLS)는 PostgreSQL의 보안 기능으로, 데이터베이스 행(row) 수준에서 접근을 제어합니다. 즉, 사용자별로 볼 수 있는 데이터 행을 제한할 수 있습니다.

Supabase에서는 기본적으로 새 테이블을 만들 때 RLS가 활성화되어 있으며, 명시적인 정책을 설정하지 않으면 모든 데이터 접근이 거부됩니다.

RLS의 주요 이점:

  • 애플리케이션 수준이 아닌 데이터베이스 수준에서 보안 적용
  • 사용자별 데이터 접근 제어
  • 실수로 인한 데이터 유출 방지

5-2. RLS 설정 방법

Supabase 대시보드에서 RLS 정책을 설정하는 방법:

  1. 대시보드에서 "Authentication" > "Policies" 탭으로 이동
  2. 정책을 설정할 테이블 선택
  3. "New Policy" 버튼 클릭
  4. 정책 유형 선택 (SELECT, INSERT, UPDATE, DELETE, ALL)
  5. 정책 이름 지정 및 정책 규칙 작성
  6. "Save" 버튼 클릭

또는 SQL 에디터에서 직접 정책 설정:

sql
-- 모든 사용자가 users 테이블의 데이터를 읽을 수 있도록 허용
CREATE POLICY "사용자 데이터 읽기 허용" ON users
FOR SELECT USING (true);

5-3. SELECT / INSERT / DELETE / UPDATE 각각 설정 방법

각 작업별로 RLS 정책을 설정하는 방법과 예시를 살펴보겠습니다.

SELECT 정책 (데이터 읽기)

모든 로그인한 사용자가 모든 데이터 조회 가능:

sql
CREATE POLICY "인증된 사용자 읽기 허용" ON posts
FOR SELECT USING (auth.role() = 'authenticated');

자신의 게시물만 조회 가능:

sql
CREATE POLICY "자신의 게시물만 읽기 허용" ON posts
FOR SELECT USING (auth.uid() = user_id);

공개된 게시물은 모든 사용자가 조회 가능:

sql
CREATE POLICY "공개 게시물 읽기 허용" ON posts
FOR SELECT USING (is_public = true);

INSERT 정책 (데이터 생성)

로그인한 사용자만 게시물 생성 가능:

sql
CREATE POLICY "인증된 사용자 삽입 허용" ON posts
FOR INSERT WITH CHECK (auth.role() = 'authenticated');

자신의 데이터만 삽입 가능하도록 제한:

sql
CREATE POLICY "자신의 ID로만 게시물 생성 가능" ON posts
FOR INSERT WITH CHECK (auth.uid() = user_id);

UPDATE 정책 (데이터 수정)

자신의 게시물만 수정 가능:

sql
CREATE POLICY "자신의 게시물만 수정 허용" ON posts
FOR UPDATE USING (auth.uid() = user_id);

관리자는 모든 게시물 수정 가능:

sql
CREATE POLICY "관리자 수정 허용" ON posts
FOR UPDATE USING (
  auth.uid() IN (
    SELECT id FROM users WHERE is_admin = true
  )
);

DELETE 정책 (데이터 삭제)

자신의 게시물만 삭제 가능:

sql
CREATE POLICY "자신의 게시물만 삭제 허용" ON posts
FOR DELETE USING (auth.uid() = user_id);

오래된 게시물만 삭제 가능:

sql
CREATE POLICY "오래된 게시물 삭제 허용" ON posts
FOR DELETE USING (created_at < NOW() - INTERVAL '1 year');

RLS 정책 디버깅 팁:

  1. 임시로 RLS를 비활성화하여 문제 확인:
sql
ALTER TABLE your_table DISABLE ROW LEVEL SECURITY;
  1. 정책이 의도대로 작동하는지 테스트:
sql
-- 특정 조건에서 true/false를 반환
SELECT auth.uid() = user_id FROM posts WHERE id = 'some-post-id';
  1. Supabase 클라이언트의 auth.uid() 확인:
javascript
const { data: { user } } = await supabase.auth.getUser();
console.log('현재 인증된 사용자 ID:', user?.id);

6. 추가 기능: Supabase의 실시간(Realtime) 기능

Supabase는 데이터베이스 변경 사항을 실시간으로 클라이언트에 전송하는 기능을 제공합니다. 이를 통해 채팅 앱, 실시간 대시보드, 협업 도구 등을 쉽게 구현할 수 있습니다.

실시간 기능 활성화:

  1. Supabase 대시보드에서 "Database" > "Replication" 탭으로 이동
  2. 실시간 기능을 활성화할 테이블 선택
  3. "Enable Realtime" 체크

React에서 실시간 구독 사용:

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

function RealtimePostsList() {
  const [posts, setPosts] = useState([]);

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

    // 실시간 구독 설정
    const channel = supabase
      .channel('public:posts')
      .on('postgres_changes',
        {
          event: '*',
          schema: 'public',
          table: 'posts'
        },
        (payload) => {
          console.log('실시간 변경 감지:', payload);

          // 데이터 변경 유형에 따라 상태 업데이트
          if (payload.eventType === 'INSERT') {
            setPosts(prev => [...prev, payload.new]);
          } else if (payload.eventType === 'UPDATE') {
            setPosts(prev =>
              prev.map(post => post.id === payload.new.id ? payload.new : post)
            );
          } else if (payload.eventType === 'DELETE') {
            setPosts(prev =>
              prev.filter(post => post.id !== payload.old.id)
            );
          }
        }
      )
      .subscribe();

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

  async function fetchPosts() {
    const { data, error } = await supabase
      .from('posts')
      .select('*')
      .order('created_at', { ascending: false });

    if (error) {
      console.error('게시물을 불러오는 중 오류 발생:', error);
      return;
    }

    setPosts(data);
  }

  return (
    <div>
      <h2>실시간 게시물 목록</h2>
      <ul>
        {posts.map(post => (
          <li key={post.id}>
            <h3>{post.title}</h3>
            <p>{post.content}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

7. 결론

Supabase는 백엔드 개발 경험이 부족한 프론트엔드 개발자에게 훌륭한 솔루션입니다. PostgreSQL의 강력함과 Firebase의 편리함을 결합하여, 빠르게 확장 가능한 애플리케이션을 구축할 수 있습니다.

주요 장점:

  • 오픈 소스
  • 관계형 데이터베이스 사용
  • SQL의 모든 기능 활용
  • 쉬운 마이그레이션
  • 강력한 보안 메커니즘 (RLS)
  • 실시간 기능

Supabase를 사용하면서 데이터베이스 개념과 SQL에 익숙해진다면, 백엔드 개발 역량도 함께 향상될 것입니다. 이는 풀스택 개발자로 성장하는 데 큰 도움이 될 것입니다.

시작하기 쉬운 Supabase로 여러분의 다음 프로젝트를 빠르게 구축해보세요!

참고 자료

  • Supabase 공식 웹사이트
  • A Supa-Introduction to Supabase
  • Supabase Realtime 개념
  • Supabase로 백엔드 없이 Database 구축하기(기본 사용법)
  • React 프로젝트에 Supabase 적용하기