이전 글에서는 Next.js 웹과 익스텐션에서 통합해 로그인을 관리하는 방법에 대해 알아보았다. 이번에는 Supabase 세팅을 마친 후 Database를 만들고 데이터를 저장하는 방법, 그리고 관련된 메서드를 관리하는 방법에 대해 알아보고자 한다.
Supabase Database 세팅
Database에 대해
Supabase Database는 PostgreSQL을 기반으로 하는 관계형 데이터베이스로, 테이블 간의 관계를 정의하고 데이터를 구조화하여 저장할 수 있다. Supabase는 PostgreSQL의 모든 기능을 지원하면서도, 직관적인 대시보드를 통해 데이터베이스 관리를 용이하게 만들어준다. 또한 자동으로 생성되는 API를 통해 클라이언트에서 쉽게 데이터베이스에 접근할 수 있다.
여기서 짚고 넘어가야할 Database 관련된 개념들이 있다.
- Database : 데이터를 체계적으로 저장·관리하는 곳
Supabase에서는 PostgreSQL 데이터베이스를 사용하는데, 각 프로젝트는 하나의 데이터베이스를 가지며 이 안에 여러 스키마와 테이블을 포함할 수 있다.
- Schema : 테이블 같은 객체들을 묶어 관리하는 네임스페이스
Supabase에서는 기본적으로 public 스키마를 제공하며, 필요에 따라 추가 스키마를 생성하여 데이터를 논리적으로 분리할 수 있다. 마치 서랍장에서 옷의 종류별로 서랍을 나누는 것처럼, 관련된 테이블들을 하나의 스키마로 묶어서 관리하는 거라고 생각하면 된다.
- Table : 데이터를 행과 열로 담는 기본 단위
각 테이블은 특정 엔티티(예: 사용자, 게시물)를 나타내며, 관련된 데이터를 체계적으로 저장한다.
- Row : 테이블의 한 행, 곧 데이터 레코드 하나
예를 들어, 사용자 테이블에서 각 행은 개별 사용자의 정보를 포함한다.
- Column : 테이블의 한 열, 곧 데이터의 속성 하나
각 열은 고유한 데이터 타입(문자열, 숫자, 날짜 등)을 가지며, 해당 속성에 대한 제약조건을 설정할 수 있다.
예를들어 웹 메모라는 메모를 저장하는 서비스가 있다고 가정해보자. 하나의 서비스이기에 Database는 web-memo라는 하나의 Database를 가질 수 있다. schema에는 사용자에 관한 정보를 저장하는 auth, 메모에 관한 정보를 저장하는 memo가 있을 것이다. auth라는 schema에는 사용자의 기본적인 정보(이메일, 비밀번호, 프로필 등)를 저장하는 users 테이블, 리프래시 토큰을 저장하는 refresh_tokens 테이블 등이 있을 것이다. 그리고 memo 스키마에는 메모의 내용을 저장하는 memo 테이블, 메모의 카테고리를 저장하는 category 테이블 등이 있을 것이다. 그리고 memo 테이블의 칼럼에는 메모를 식별하는 고유값인 id, 메모 데이터를 저장하는 memo, 메모를 저장한 사용자 정보를 저장하는 user_id, 메모의 제목을 저장하는 title이 있을 수 있다.
위와 같이 하나의 데이터베이스에는 데이터베이스 객체들을 그룹화하는 여러 개의 스키마가 존재하며, 각 스키마에는 특정 도메인과 관련된 데이터를 저장하고 관리하는 여러 개의 테이블로 구성되어 있고, 테이블에는 실제 데이터들을 담고 있다. 각 데이터를 담은 테이블은 행과 열로 구성된 2차원 구조를 가지고 있으며, 각 행은 하나의 완전한 데이터 레코드를, 열은 그 데이터의 특정 속성을 정의한다.
Supabase에서 데이터베이스 세팅하기
그럼 이제 Supabase의 Database를 세팅해보자.
- Project 생성
Supabase에서는 Oragnization 즉 조직 아래에 여러 개의 프로젝트를 만들 수 있다. 하나의 프로젝트는 독립적인 데이터베이스 환경을 제공한다.
프로젝트를 생성할 때는 지역, 데이터베이스 비밀번호, 가격 정책 등을 설정할 수 있으며, 생성이 완료되면 프로젝트의 대시보드에서 데이터베이스 관리를 시작할 수 있다.
- 커스텀 schema 생성
기본적으로 public이라는 schema가 존재한다. 하지만 이번에는 새로운 스키마를 만들어 관리해보자.
Supabase에서 데이터베이스를 관리할 때 대시보드의 GUI를 사용해도 된다. 하지만 조금 더 유연하고 간편하게 관리하기 위해서는 SQL을 사용하는 것도 좋은 방법이다.
좌측 네비게이션 바의 SQL Editor에 들어가 SQL을 생성하고, 관리하고, 실행할 수 있다.
CREATE SCHEMA test;
스키마를 생성하는 명령어다.
- 커스텀 스키마 노출하기
API Settings > Exposed schemas에 2번에서 생성한 커스텀 스키마 추가한다.
그리고 SQL에서 아래 명령문을 실행한다.
GRANT USAGE ON SCHEMA test TO anon, authenticated, service_role;
GRANT ALL ON ALL TABLES IN SCHEMA test TO anon, authenticated, service_role;
GRANT ALL ON ALL ROUTINES IN SCHEMA test TO anon, authenticated, service_role;
GRANT ALL ON ALL SEQUENCES IN SCHEMA test TO anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA test GRANT ALL ON TABLES TO anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA test GRANT ALL ON ROUTINES TO anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA test GRANT ALL ON SEQUENCES TO anon, authenticated, service_role;
위에서 test는 내가 생성한 스키마의 이름이며 이를 각자가 만든 스키마 이름으로 수정하면 된다.
이 명령어들이 하는 일을 간단히 설명하자면, 생성한 스키마에 대한 권한을 설정하는 것이다. anon(미인증 사용자), authenticated(인증된 사용자), service_role(서비스 역할)에게 스키마의 모든 테이블, 루틴, 시퀀스에 대한 접근 권한을 부여한다. 이는 클라이언트 애플리케이션에서 해당 스키마의 데이터에 접근할 수 있도록 하기 위해 필요한 설정이다.
- Table Editor에서 확인
이제 내가 만든 스키마를 선택할 수 있다.
- 테이블 및 칼럼 생성
이제 스키마 내에 테이블과 칼럼을 자유롭게 생성하고 수정할 수 있다.
Supabase 데이터베이스 예시
Supabase에서는 스키마를 시각화해서 볼 수 있는 기능을 제공한다. memo 스키마를 살펴보면 다음과 같다.
memo 테이블을 보면 id는 unique인 동시에 primary키값이고, memo, user_id, url, title은 필수값이다. category 테이블도 마찬가지로 id는 unique인 동시에 primary키값이며, name과 created_at은 필수값이다.
여기서 중요한 부분은 테이블 간의 관계 설정이다. memo의 category_id는 category의 id를 외래키로 참조하고 있다. 이를 통해 각 메모는 하나의 카테고리와 연결될 수 있으며, 데이터의 일관성과 참조 무결성을 보장할 수 있다. 실제로 이런 관계 설정을 해두면 나중에 카테고리를 삭제하려고 할 때 해당 카테고리를 사용 중인 메모가 있으면 삭제를 막아주는 등의 안전장치가 자동으로 작동한다.
그리고 memo와 category의 user_id는 auth 스키마의 user 테이블의 id를 외래키로 참조하고 있다. 이를 통해 메모와 카테고리는 각각 사용자 데이터와 연결해, 데이터의 소유권과 접근 권한을 명확하게 관리할 수 있다.
Supabase RLS 세팅
RLS는 Row Level Security의 약자로, 데이터베이스 수준에서 각 행(row)에 대한 접근 권한을 제어하는 보안 기능이다. 이를 통해 사용자별로 데이터 접근을 제한하고 민감한 정보를 보호할 수 있다. Supabase에서는 SQL 정책을 사용해 RLS를 구현할 수 있다.
처음에 RLS를 접했을 때는 "이게 정말 필요한가?" 싶었는데, 실제로 서비스를 운영하다 보니 이게 얼마나 중요한 보안 기능인지 체감하게 됐다. 만약 RLS 설정을 하지 않았다면 API 키만 알면 다른 사용자의 데이터까지 모두 접근할 수 있게 되기 때문이다.
RLS를 사용해 우리가 설정하고 싶은 데이터 접근 권한은 다음과 같다.
: 메모를 조회, 수정, 제거, 업데이트 모두 본인만 할 수 있다.
이는 서비스 특성에 따라 상이할 수 있으니 서비스의 요구사항에 맞게 체크를 해야한다. 예를 들어 SNS 서비스라면 다른 사용자의 게시물을 조회할 수 있어야 하지만, 수정과 삭제는 본인만 가능해야 할 것이다.
위 요구사항에 맞게 권한을 설정해보자.
- RLS 활성화
만약 RLS를 활성화하지 않았다면 SQL문에서 아래 스크립트를 실행해 RLS를 활성화하자.
alter table "table_name" enable row level security;
RLS를 활성화할 시 policy를 만들기 전까지 public anon키로는 API를 이용한 어떤 데이터도 접근할 수 없다. 여기서 anon은 인증되지 않은(즉, 로그인하지 않은) 요청을 의미한다.
- Authentication > Policies > schema 선택
- Create Policy 클릭
위와 같이 (( SELECT auth.uid() AS uid) = user_id)로 설정할 경우 현재 로그인한 사용자의 ID와 데이터의 user_id 필드가 일치하는지 확인해 사용자가 자신의 데이터만 접근할 수 있도록 제한할 수 있다. 여기서 auth.uid()는 요청을 한 사용자의 ID를 반환하는 Supabase의 헬퍼 함수다.
이로써 각자 사용자는 각자의 메모 데이터만 접근을 할 수 있게 된다.
RLS 정책
create policy "Individuals can view their own todos."
on todos for select
using ( (select auth.uid()) = user_id );
위 코드는 SQL의 정식 문법이 아니다. 이는 Supabase RLS 정책 문법으로 더 간단하게 PostgreSQL의 RLS를 설정할 수 있도록 만든 특별한 문법이다.
위 문법은 실제로 테이블에 접근 시 아래와 같이 변환이 되어 실행된다.
select *
from todos
where auth.uid() = todos.user_id;
-- Policy is implicitly added.
사용자가 todos 테이블을 조회할 때마다 자동으로 이 WHERE 조건이 추가되어, 자신의 user_id와 일치하는 데이터만 볼 수 있게 되는 것이다. 이 방식의 장점은 클라이언트 코드에서는 아무런 추가 로직 없이 그냥 평범하게 데이터를 조회하기만 하면 된다는 점이다. 보안 로직이 데이터베이스 레벨에서 자동으로 처리되니 실수로 권한 체크를 빼먹을 일도 없다.
RLS 정책의 다양한 케이스
- 조회는 모든 사용자가 가능
create policy "Public profiles are visible to everyone."
on profiles for select
to anon -- the Postgres Role (recommended)
using ( true ); -- the actual Policy
- 인증된 사용자만 조회 가능
create policy "Authenticated users can view data"
on table_name for select
to authenticated
using ( auth.role() = 'authenticated' );
- 특정 role을 가진 사용자만 수정 가능
create policy "Only admins can update"
on table_name for update
to authenticated
using ( auth.jwt() ->> 'role' = 'admin' );
이런 정책은 관리자 대시보드를 만들 때 유용하다. JWT 토큰에 저장된 role 정보를 확인해 admin인 경우에만 데이터 수정을 허용하는 방식이다.
- 자신의 데이터만 삭제 가능
create policy "Users can delete their own data"
on table_name for delete
to authenticated
using ( auth.uid() = user_id );
- 특정 조건을 만족하는 데이터만 삽입 가능
create policy "Users can only insert active status"
on table_name for insert
to authenticated
with check ( status = 'active' );
insert 정책에서는 with check 절을 사용한다는 점이 select나 update와 다른 부분이다. 이는 새로 삽입되는 데이터가 특정 조건을 만족하는지 검증하는 역할을 한다.
- 자신의 데이터만 수정 가능 (update)
create policy "Users can update their own data"
on table_name for update
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );
update 정책에서 짚고 넘어갈 부분이 있다. update는 using과 with check를 둘 다 써야 한다는 점이다. using은 '어떤 행을 수정 대상으로 삼을 수 있는가'를 거르는 조건이고, with check는 '수정한 결과가 이 조건을 만족하는가'를 검사하는 조건이다. 만약 using만 걸면 내 데이터만 수정 대상으로 잡히긴 하지만, 수정하면서 user_id를 남의 값으로 바꿔치기하는 걸 막지 못한다. 반대로 with check만 걸면 애초에 수정 대상을 거르지 못한다. 그래서 '내 것만 골라서, 내 것인 상태로만 저장되게' 하려면 둘 다 필요하다.
Supabase 타입 및 메서드 관리
1) Supabase 스키마 타입 생성
Supabase에서는 Type을 자동으로 생성하는 CLI를 제공한다. 이건 정말 편리한 기능인데, 데이터베이스 스키마가 변경될 때마다 수동으로 타입을 업데이트할 필요가 없어진다. 스크립트 예시는 다음과 같다.
npx supabase gen types --lang=typescript --project-id ${프로젝트 ID} --schema ${스키마명} > packages/shared/src/types/supabase.ts
프로젝트 ID의 스키마에 맞는 타입을 원하는 위치에 typescript파일로 만드는 스크립트다.
생성된 파일은 prettier가 적용이 안되어있기에 prettier까지 적용을 하고, 이를 package.json의 scripts에 추가하면 다음과 같다.
// package.json
{
"scripts":{
"generate-supabase-type": "supabase gen types --lang=typescript --project-id ${프로젝트 아이디} --schema ${스키마명} > ${타입을 저장할 위치} && prettier --write ${타입을 저장한 위치}"
// ..
}
}
이렇게 설정해두면 데이터베이스 스키마를 수정한 후 npm run generate-supabase-type 명령어 하나로 타입을 최신 상태로 동기화할 수 있다. 특히 팀 프로젝트에서는 이 스크립트를 공유해두면 모든 팀원이 동일한 타입을 사용할 수 있어서 협업하기 훨씬 수월해진다.
2) Supabase 메서드 관리
Supabase에서는 JavaScript를 이용한 다양한 메서드를 제공한다. 이전 글에서 만든 SupabaseClient를 이용해 select, insert, update, upsert, delete 등의 메서드를 사용할 수 있다. 이런 메서드를 매번 작성하는 일은 유지보수성을 떨어뜨리고, 에러를 잠재할 수 있는 행위다. 왜냐하면 매 번 아래와 같은 코드를 작성한다고 해보자.
supabaseClient.from(SUPABASE.table.memo).insert(request).select()
한 두 곳에서 사용한다면 틀리지 않고 동일하게 작성할 수 있다. 하지만 다른 곳에서는 특정 id만을 검색하기 위해 .eq('id', id)를 추가했다면? 이는 각 사용하는 곳마다 사용 방법이 상이할 수 있음을 내포하고 이는 예측 가능성을 떨어뜨린다. A라는 곳에서는 모든 데이터를 불러오는 API로 쓰였지만 다른 곳에서는 특정 id에 해당하는 데이터만 불러오는 API로 사용이 되었기 때문이다. 또한 한 곳을 수정하면 다른 곳 또한 수정을 해야하기에 유지 보수성이 떨어지고, 이는 곧 에러를 잠재하고 있을 가능성을 만든다. 그렇기에 중앙에서 관리하는 것이 예측 가능성, 유지 보수성과 안정성 면에서 유리하다.
실제로 프로젝트를 진행하면서 여러 곳에서 비슷한 쿼리를 반복해서 작성하다가 나중에 테이블 구조가 변경됐을 때 모든 곳을 찾아다니며 수정하느라 고생했던 경험이 있다. 그 이후로는 무조건 중앙 집중 관리 방식을 사용하고 있다.
나는 shared라는 공통 패키지에서 클래스를 만들어 중앙 집중 관리를 했다. 각각의 함수로 만들 경우 각 함수의 이름을 기억해야하는 것이 번거로울 것이라 판단했다. 클래스로 묶을 경우 '메모와 관련된 메서드 뭐가있지?'하며 'MemoService'를 호출하면 사용 가능한 모든 메서드를 볼 수 있기 때문이다. VS Code에서 MemoService.만 입력하면 자동완성으로 모든 메서드가 쭉 나오는데, 이게 생각보다 개발 경험을 많이 향상시켜준다.
또한 객체가 아니라 클래스를 사용한 이유는 프로젝트의 통일성을 위해서다. 객체는 프로젝트 내에서 주로 상수를 저장하는 용도로 사용되었고 클래스는 비즈니스 로직을 담당하는 서비스나 유틸리티 함수를 그룹화하는데 사용했다. 그렇기에 Supabase의 메서드를 그룹화하는데에도 클래스를 사용하는 것이 적절하다고 판단했다.
Supabase의 메서드를 묶은 클래스 MemoService를 살펴보자.
- MemoService의 생성자 메서드
export class MemoService {
supabaseClient: MemoSupabaseClient;
constructor(supabaseClient: MemoSupabaseClient) {
this.supabaseClient = supabaseClient;
}
// ..
}
생성자 메서드 내부에서 Supabase Client를 인자로 전달받아 이를 클래스의 멤버 변수로 저장한다. 이렇게 저장된 클라이언트는 다른 메서드에서 데이터베이스 작업을 수행할 때 사용된다. 의존성 주입 패턴을 사용한 건데, 이렇게 하면 나중에 테스트할 때 mock 클라이언트를 주입하기도 쉽고, 클라이언트 설정이 변경되어도 서비스 코드를 수정할 필요가 없다.
- MemoService의 각종 메서드
async insertMemo(request: MemoTable['Insert']) {
return this.supabaseClient.from(SUPABASE.table.memo).insert(request).select();
}
async upsertMemos(request: MemoTable['Insert'][]) {
return this.supabaseClient.schema(SUPABASE.table.memo).from(SUPABASE.table.memo).upsert(request).select();
}
async getMemos() {
return this.supabaseClient
.schema(SUPABASE.table.memo)
.from(SUPABASE.table.memo)
.select('*,category(name)')
.order('created_at', { ascending: false });
}
// ..
}
여기서 간결한 코드 작성을 위해 화살표 함수를 사용할 수 있지만 그럼에도 위와 같이 일반 메서드를 사용할 것을 권장한다. 왜냐하면 클래스 내 화살표 함수로 메서드를 등록할 경우 이는 인스턴스 속성으로 저장되어 프로토타입 체인에 등록되지 않는다. 다시말해 인스턴스를 생성할 때마다 메서드를 새롭게 생성하는 것을 의미한다. 이는 메모리 사용량을 증가시키고 상속을 통한 메서드 재사용을 어렵게 만든다. 반면 일반 메서드는 프로토타입 체인에 등록되어 메모리를 효율적으로 사용하고 상속을 통한 재사용이 가능하다.
❗ 주의할 점
여기서 주의할 점이 있다. 위와 같이 일반 메서드로 선언후 react-query를 사용할 때 함수 그 자체를 넘겨주면 안된다는 것이다.
return useMutation<MemoSupabaseResponse, MutationError, MemoTable['Insert']>({ mutationFn: new MemoService(supabaseClient).insertMemo, // .. }함수 자체를 넘겨줄 경우 이 함수를 실행하는 주체는 react-query의 useMutation가 된다. 다시말해
new AuthService(supabaseClient).getUser함수가 useMutation의 인자로 넘어가 아래 함수가 useMutation에 의해 실행된다는 것을 의미한다.async insertMemo(request: MemoTable['Insert']) { return this.supabaseClient.from(SUPABASE.table.memo).insert(request).select(); }useMutation에 의해 insertMemo가 실행될 때 this는 어디를 가리킬까?
그렇다. 전역 객체이다. 왜냐하면 함수 자체가 콜백 함수로 전달되었기에 함수를 실행하는 주체인 useMutation이 this를 결정하고, 이 때 인스턴스의 메서드가 아닌 일반 함수로 실행이 되기에 this는 전역 객체가 된다.
그럼 이를 어떻게 해결할까?
함수를 새롭게 생성해 전달한다.
return useMutation<MemoSupabaseResponse, MutationError, MemoTable['Insert']>({ mutationFn: (...request) => new MemoService(supabaseClient).insertMemo(...request), // .. }기존 방식의 문제점은 함수를 실행하는 주체가 useMutation이라는 것이었다. useMutation 내에서 함수를 직접 실행하기에 this가 바뀐 것이다.
위와 같이 함수를 새롭게 생성해 전달할 경우 insertMemo메서드는 MemoService라는 클래스의 인스턴스 메서드로써 호출이 된다. 즉 useMutation내부에서 이 함수를 실행할 때 메서드로 호출이 되어 this가 클래스 인스턴스에 바인딩이 된다는 말을 의미한다.
클래스 내에서 일반 메서드가 아니라 화살표 함수로 선언한다.
class MemoService { constructor(private supabaseClient: SupabaseClient) {} // 화살표 함수로 선언 insertMemo = async (request: MemoTable['Insert']) => { return this.supabaseClient.from(SUPABASE.table.memo).insert(request).select(); } }화살표 함수는 일반 메서드와 달리 함수를 호출할 당시 this바인딩을 하지 않는다. 그렇기 때문에 화살표 함수로 선언할 경우 this는 상위 실행 컨텍스트인 클래스 인스턴스를 가리키게 된다.
bind를 사용해 명시적으로 this를 바인딩한다.
const memoService = new MemoService(supabaseClient); return useMutation<MemoSupabaseResponse, MutationError, MemoTable['Insert']>({ mutationFn: memoService.insertMemo.bind(memoService), // .. });bind 메서드를 사용하여 함수의 this를 명시적으로 바인딩할 수도 있다. 이는 함수를 전달할 때 this의 컨텍스트를 유지하는 방법이다. 여기서 주의할 점은 인스턴스를 딱 하나만 만들어 그 인스턴스를 bind 대상으로 넘겨야 한다는 것이다. 위처럼
new MemoService(...)를 두 번 부르면 실제로 호출되는 인스턴스와 this로 묶인 인스턴스가 서로 다른 객체가 되어버린다.
더 자세한 코드는 아래 파일을 참고바란다.
3) 사용
자, 이제 세팅과 메서드 관리를 했으니 사용을 해보자.
// useMemosQuery.ts
const query = useSuspenseQuery<QueryFnData, QueryError, QueryData>({
queryFn: new MemoService(supabaseClient).getMemos,
// ..
// src/app/memos/layout.tsx
const isUserLogin = await new AuthService(supabaseClient).checkUserLogin();
이렇게 사용하면 어디서든 동일한 방식으로 Supabase 메서드를 호출할 수 있고, 나중에 쿼리 로직을 수정해야 할 때도 MemoService 클래스 한 곳만 수정하면 된다. 실제로 프로젝트를 진행하면서 테이블 구조가 변경되거나 필터링 조건을 추가해야 할 때 정말 편리했다.
맺으며
이번 글에서는 Supabase Database 설정부터 Supabase 스키마 생성과 테이블 설정, RLS를 통한 사용자별 데이터 접근 권한을 설정하는 방법 등에 대해 알아보았다. 또한 메서드 관리에서는 클래스를 활용한 중앙 집중식 관리 방식의 장점과 주의해야 할 점들을 자세히 살펴보았다.
Supabase 같은 BaaS(Backend as a Service)는 간단한 서비스를 만들기에 잘 맞는다. 백엔드 인프라를 직접 구축하는 부담 없이도 안정적이고 확장 가능한 데이터베이스를 쓸 수 있기 때문이다. 또한 Supabase는 인증과 데이터베이스가 긴밀하게 통합되어 있어 개발자가 보안과 데이터 관리에 더 집중할 수 있다는 장점이 있다.
개인적으로 Supabase를 사용하면서 가장 만족스러웠던 부분은 백엔드 없이도 프로덕션 레벨의 서비스를 만들 수 있다는 점이었다. PostgreSQL을 이런 인터페이스로 쓸 수 있다는 게 특히 좋았다. 물론 복잡한 비즈니스 로직이 필요하거나 대규모 트래픽을 처리해야 하는 경우에는 별도의 백엔드 서버가 필요할 수 있지만, 많은 경우 Supabase만으로도 충분하다.
그 외 Supabase는 Realtime, Storage, Edge Functions 등 다양한 기능들을 제공한다. 이걸 어디에 써먹을지 벌써 궁금하다. 특히 Realtime 기능은 실시간 협업 도구를 만들 때 정말 유용하게 쓸 수 있을 것 같고, Edge Functions는 복잡한 비즈니스 로직을 처리하는데 활용할 수 있을 것 같다.
Supabase같은 BaaS서비스는 특히 나같이 개인 프로젝트를 만들고 싶은 프론트엔드 개발자에게 큰 힘이 된다. 백엔드 개발자가 없이 혼자서 웹 혹은 앱을 만드는 개발자들에게 Supabase를 강력하게 추천한다. 아이디어만 있다면 빠르게 프로토타입을 만들고 검증할 수 있다는 게 정말 큰 장점이다.
다음번에는 Realtime, Storage, Edge Functions라는 날개를 서비스에 달은 이야기를 가져오도록 하겠다.