Supabase Quick Start
Supabase란?
Backend-as-a-Service(BaaS) 서비스.
대표적으로 AWS Amplify, Firebase, Supabase가 있다.
관계형 데이터베이스를 지원하는 것이 특징이다.
무료 요금제 사용량
- 무제한 API dycjd
- 월 50,000명의 활성 사용자
- 500mb 데이터베이스 저장 공간, 2 Core CPU, RAM 1GB
- 1gb의 파일 스토리지(파일 저장 공간)
- 월 제공 대역폭 5GB
사용 방법
회원가입
https://supabase.com/dashboard/sign-in?returnTo=/projects
Supabase
By continuing, you agree to Supabase's Terms of Service and Privacy Policy, and to receive periodic emails with updates.
supabase.com
Supabase 접속 - Continue with GitHub - (깃허브 페이지가 열리면) Authorize Supabase
프로젝트 생성 및 데이터 베이스 셋업
New Project 클릭
데이터베이스 기본 사항 입력
- 데이터베이스 패스워드는 웬만한 경우 사용할 일이 없으니 Generate a password 클릭해도 무방.
- 지역은 서비스 주요 사용자와 가장 가까운 곳으로 선택.
대시보드 확인
- 왼쪽 사이드바에서 데이터 관련 메뉴들이 나옴.
- 대시보드에 퍼블릭 키와 시크릿 키가 나옴. 추후 환경변수 처리하여 감추는 것이 보안상 좋음.
데이터 테이블 생성 (Table Editor)
샘플 데이터 입력해보기
정책 수정하기 (Add RLS Policy)
데이터 테이블에 접근해서 데이터를 추가, 수정, 삭제할 수 있는 권한을 설정해야 한다.
Add RLS policy - Create policy 선택
우측 사이드 패널에서 정책 템플릿이 나옴.
- SELECT : Enable read access for all users
- 모든 사용자가 데이터 테이블의 읽기 권한을 가짐.
- INSERT : Enable insert fore authenticated users only
- 인증된 사용자만 데이터를 삽입할 수 있도록 함. (auth에 정보가 있어야 함.)
- UPDATE : Enable update for users based on email
- 사용자가 자신의 이메일에 해당하는 데이터만 업데이트 할 수 있음. (auth와 데이터 테이블 모두 email이 있고 일치해야 함)
- DELETE : Enable delete for users based on user_id
- 사용자가 자신의 user_id로 생성한 데이터만 삭제할 수 있음.
- INSERT : Enable insert for users based on user_id
- 사용자가 자신의 user_id에 해당하는 데이터만 입력할 수 있음. 데이터 테이블에 user_id가 미리 입력되어 있어야 함.
- UPDATE : Policy with table joins
- 두 개 이상의 데이터 테이블을 조인하여 접근 규칙을 설정함.
- 예를 들어서 user 테이블의 user_id와 post 테이블의 user_id만 일치하는 행에만 접근 가능하도록.
일반적인 경우 SELECT(Enable read access for all users), INSERT(Enable insert for authenticated user only), Update(Enable update for users based on email), DELETE(Enable delete for users based on user_id)을 선택해주면 보안이 적용된 CRUD가 가능함.
단, 템플릿 상으로는 삭제는 user_id에 기반하고, 업데이트는 email에 기반하니 이를 SQL 쿼리로 email이 아니라 user_id에 기반하도록 수정해주어도 됨.
데이터 테이블 컬럼으로 다시 돌아와 컬럼을 추가해준다.
- Name: user_id (자유 작명)
- Type: uuid
- Default Value: auth.uid()
아래는 필수 사항은 아니나 foreign key 설정으로, 외부 테이블(이 경우 auth 테이블)과 데이터 테이블을 연결하여, 회원탈퇴 시 그 user_id로 작성된 모든 데이터가 삭제되는 Cascade 설정도 할 수 있다.
데이터 삭제 코드
const { error } = await supabase
.from('countries')
.delete()
.eq('id', 1)
회원 관리 기능 구현
이메일 가입 / 소셜 로그인 기능 활성화
GitHub 연동하는 방법
(GitHub에서) GitHub 로그인 -> 우측 상단 프로필 사진 클릭 -> Settings 클릭 -> 페이지 좌측 하단 Developer settings 클릭 -> 타사 인증인 OAuth Apps 클릭 -> New OAuth App 클릭 -> Clients sectets 키를 Generate a new client secret 버튼 클릭하여 생성 -> 생성된 Client ID와 Client secrets 값을 복사 (Client secrets 값은 절대 노출되면 안 됨)
(Supabase에서) GitHub 클릭 후 Disabled를 Enabled로 전환 -> 복사한 Client ID와 Client secrets를 붙여 넣기 -> Done을 눌러 활성화
GitHub 로그인 / 로그아웃 코드 (공식문서 제공)
// 로그인 코드
async function signInWithGithub() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github',
})
}
// 로그아웃 코드
async function signOut() {
const { error } = await supabase.auth.signOut()
}
// 로그인 여부를 알 수 있는 코드
async function checkSignIn() {
const session = await supabase.auth.getSession();
const isSignIn = !!session.data.session;
setSignIn(isSignIn);
}
전체 코드 샘플 예제
import { useEffect, useState } from 'react';
import { createClient } from '@supabase/supabase-js';
const supabase = createClient("https://<project>.supabase.co", "<your-anon-key>");
function App() {
const [posts, setPosts] = useState([]);
const [signIn, setSignIn] = useState(false);
async function getPosts() {
const { data } = await supabase.from('posts').select();
setPosts(data);
}
async function signInWithGithub() {
await supabase.auth.signInWithOAuth({
provider: 'github',
});
}
async function checkSignIn() {
const session = await supabase.auth.getSession();
const isSignIn = !!session.data.session;
setSignIn(isSignIn);
}
async function signOut() {
await supabase.auth.signOut();
checkSignIn();
}
useEffect(() => {
getPosts();
checkSignIn();
}, []);
return (
<main>
<ul>
{posts.map((post) => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.content}</p>
</li>
))}
</ul>
{signIn ? (
<SignInBtn text='로그아웃' onClick={signOut} />
) : (
<SignInBtn text='로그인' onClick={signInWithGithub} />
)}
</main>
);
}
function SignInBtn({ text, onClick }) {
return (
<button type='button' onClick={onClick}>
{text}
</button>
);
}
export default App;
리액트 프로젝트 셋업
패키지 설치
yarn add @supabase/supabase-js
퍼블릭키, 시크릿키 환경변수 처리
.env 파일 생성
시크릿키는 서버에서 필요한 경우에만 사용하고, 지금의 경우에는 필요하지 않으니 포함시키지 않는 것이 안전하다.
// .env
REACT_APP_SUPABASE_URL=https://your-project-ref.supabase.co
REACT_APP_SUPABASE_PUBLIC_KEY=나의 퍼블릭 키
.gitignore 등록
// .gitignore
.env
CURD 전체 코드 샘플
import axios from 'axios';
// 환경 변수로부터 Supabase URL 및 키를 가져옵니다.
const SUPABASE_URL = process.env.REACT_APP_SUPABASE_URL;
const SUPABASE_PUBLIC_KEY = process.env.REACT_APP_SUPABASE_PUBLIC_KEY;
// Axios 인스턴스를 설정합니다.
const axiosInstance = axios.create({
baseURL: `${SUPABASE_URL}/rest/v1/`,
headers: {
apikey: SUPABASE_PUBLIC_KEY,
Authorization: `Bearer ${SUPABASE_PUBLIC_KEY}`,
},
});
// 데이터 읽기 (Read)
export const getPlaces = async () => {
try {
const response = await axiosInstance.get('places');
return response.data;
} catch (error) {
console.error('Error fetching places:', error);
throw error;
}
};
// 데이터 추가 (Create)
export const addPlace = async (place) => {
try {
const response = await axiosInstance.post('places', place, {
headers: {
'Content-Type': 'application/json',
Prefer: 'return=representation',
},
});
return response.data;
} catch (error) {
console.error('Error adding place:', error);
throw error;
}
};
// 데이터 수정 (Update)
export const updatePlace = async (id, updatedPlace) => {
try {
const response = await axiosInstance.patch(`places?id=eq.${id}`, updatedPlace, {
headers: {
'Content-Type': 'application/json',
Prefer: 'return=representation',
},
});
return response.data;
} catch (error) {
console.error('Error updating place:', error);
throw error;
}
};
// 데이터 삭제 (Delete)
export const deletePlace = async (id) => {
try {
const response = await axiosInstance.delete(`places?id=eq.${id}`);
return response.data;
} catch (error) {
console.error('Error deleting place:', error);
throw error;
}
};
CRUD 코드 TanStack Query, Axios instance 활용 (옵션)
패키지 설치
// 필요 패키지 설치
npm install @tanstack/react-query axios
Axios 인스턴스 설정
// src/api/axiosInstance.js
import axios from 'axios';
// 환경 변수로부터 Supabase URL 및 키를 가져옴
const SUPABASE_URL = process.env.REACT_APP_SUPABASE_URL;
const SUPABASE_PUBLIC_KEY = process.env.REACT_APP_SUPABASE_PUBLIC_KEY;
// Axios 인스턴스를 설정
const axiosInstance = axios.create({
baseURL: `${SUPABASE_URL}/rest/v1/`,
headers: {
apikey: SUPABASE_PUBLIC_KEY,
Authorization: `Bearer ${SUPABASE_PUBLIC_KEY}`,
},
});
export default axiosInstance;
API 호출 유틸 함수 설정
// src/api/placesApi.js
import axiosInstance from './axiosInstance';
// 데이터 읽기 (Read)
export const fetchPlaces = async () => {
const response = await axiosInstance.get('places');
return response.data;
};
// 데이터 추가 (Create)
export const createPlace = async (place) => {
const response = await axiosInstance.post('places', place, {
headers: {
'Content-Type': 'application/json',
Prefer: 'return=representation',
},
});
return response.data;
};
// 데이터 수정 (Update)
export const updatePlace = async (id, updatedPlace) => {
const response = await axiosInstance.patch(`places?id=eq.${id}`, updatedPlace, {
headers: {
'Content-Type': 'application/json',
Prefer: 'return=representation',
},
});
return response.data;
};
// 데이터 삭제 (Delete)
export const deletePlace = async (id) => {
const response = await axiosInstance.delete(`places?id=eq.${id}`);
return response.data;
};
TanStack Query 커스텀 훅 작성
// src/hooks/usePlaces.js
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { fetchPlaces, createPlace, updatePlace, deletePlace } from '../api/placesApi';
export const usePlaces = () => {
const queryClient = useQueryClient();
// 데이터 읽기 (Read)
const { data: places, error, isPoading } = useQuery(['places'], fetchPlaces);
// 데이터 추가 (Create)
const createMutation = useMutation(createPlace, {
onSuccess: () => {
queryClient.invalidateQueries(['places']);
},
});
// 데이터 수정 (Update)
const updateMutation = useMutation(({ id, updatedPlace }) => updatePlace(id, updatedPlace), {
onSuccess: () => {
queryClient.invalidateQueries(['places']);
},
});
// 데이터 삭제 (Delete)
const deleteMutation = useMutation((id) => deletePlace(id), {
onSuccess: () => {
queryClient.invalidateQueries(['places']);
},
});
return {
places,
error,
isLoading,
createPlace: createMutation.mutate,
updatePlace: updateMutation.mutate,
deletePlace: deleteMutation.mutate,
};
};
컴포넌트에서 커스텀 훅 사용
import React from 'react';
import { usePlaces } from '../hooks/usePlaces';
const PlacesList = () => {
const { places, error, isPending, createPlace, updatePlace, deletePlace } = usePlaces();
if (isPending) return <div>로딩 중</div>;
if (error) return <div>데이터를 불러오는 중 에러가 발생했습니다.</div>;
const handleCreate = () => {
createPlace({ name: 'New Place' });
};
const handleUpdate = (id) => {
updatePlace({ id, updatedPlace: { name: 'Updated Place' } });
};
const handleDelete = (id) => {
deletePlace(id);
};
return (
<div>
<h1>Places</h1>
<button onClick={handleCreate}>Add Place</button>
<ul>
{places.map((place) => (
<li key={place.id}>
{place.name}
<button onClick={() => handleUpdate(place.id)}>Update</button>
<button onClick={() => handleDelete(place.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
};
export default PlacesList;
TanStack Query Provider 설정
// App.jsx 또는 main.jsx
import React from 'react';
import { QueryClientProvider } from '@tanstack/react-query';
import queryClient from './api/queryClient';
import PlacesList from './components/PlacesList';
const App = () => {
return (
<QueryClientProvider client={queryClient}>
<PlacesList />
</QueryClientProvider>
);
};
export default App;
스토리지 사용
이미지 파일용 Bucket 생성
사이드바 - Storage - New bucket 클릭 - Public 테이블로 설정 - 용량, 확장자 제한 설정(옵션) - 버킷 만든 후 New policy 클릭 - 모든 설정 풀기 (SELECT, INSERT, UPDATE, DELETE)
버킷 정책 설정
버킷의 권한을 푸는 방법은 Polices - New policy - For full customization - SELECT ~ DELETE까지 모든 권한 풀기
기본 이미지 설정 (옵션)
이미지를 렌더링 하는 부분에서 아무 이미지도 업로드 되지 않았을 때 기본으로 노출할 이미지를 저장해두면 좋다.
예를 들어 위의 이미지를 저장해보겠다.
버킷에서 적당한 폴더를 만든 후 이미지를 업로드 한다.
업로드한 이미지에서 점 세 개를 클릭하여 Get URL을 클릭해서 이미지 URL을 획득한다.
다음 코드에서 기본 이미지를 변수로 저장해둔다. 필요에 따라 전역에 내려줄 수 있다.
const SUPABASE_URL = process.env.REACT_APP_SUPABASE_URL;
const getDefaultImageUrl = () => {
return `${SUPABASE_URL}/storage/v1/object/public/images/default/default.jpg`;
};
'Programing > Server' 카테고리의 다른 글
supabase 유저 이메일 말고 유저 아이디가 일치하면 업데이트 가능하도록 정책 설정하기 (0) | 2024.06.19 |
---|---|
Supabase 로그인 기능 구현하기 (0) | 2024.06.18 |
Supabase 회원가입 기능 구현하기 (0) | 2024.06.18 |
Glitch를 이용해서 json-server 생성하기 (1) | 2024.06.14 |
[supabase] 댓글 관리할 테이블 만들기 (0) | 2024.06.03 |
댓글