Node.js와 Supabase 기초
출처: Dev.to
Supabase는 관리형 PostgreSQL을 중심으로 만든 오픈소스 백엔드 플랫폼입니다. 데이터베이스, 자동 생성된 REST API(PostgREST를 통해), 인증, 파일 스토리지, 실시간 구독, Edge Functions 등을 제공하며, 대시보드와 SQL 편집기가 함께 제공됩니다.
직접 Postgres를 운영하는 것과 비교했을 때, Supabase는 호스팅 인프라, API 레이어, 그리고 제품 기능들을 별도 설정 없이 바로 사용할 수 있게 해줍니다. ORM만 사용하는 스택과 비교하면, 보통 Supabase 클라이언트나 SQL을 통해 Postgres와 소통하고, RLS(행 수준 보안)가 데이터베이스 레이어에서 접근을 제어합니다.
사전 준비
-
Supabase 계정 (학습용이라면 무료 티어면 충분합니다)
-
Node.js 버전 26
-
@supabase/supabase-js설치 (npm i @supabase/supabase-js)
프로젝트와 데이터베이스 만들기
-
Supabase 대시보드에서 New project를 선택하고, 지역을 고른 뒤 데이터베이스 비밀번호를 설정합니다.
-
프로젝트가 준비될 때까지 기다립니다.
-
SQL Editor를 열고 아래 스키마를 실행합니다.
Table Editor는 빠른 실험에 좋지만, SQL Editor를 사용하면 스키마를 Git에 저장하고 리뷰할 수 있어 재현성이 높아집니다.
create table if not exists public.todos (
id bigint generated always as identity primary key,
title text not null,
done boolean not null default false,
created_at timestamptz not null default now()
);
create or replace function public.list_open_todos()
returns setof public.todos
language sql
security definer
set search_path = public
as $$
select * from public.todos where done = false order by id;
$$;
create or replace function public.mark_todo_done(todo_id bigint)
returns setof public.todos
language sql
security definer
set search_path = public
as $$
update public.todos set done = true where id = todo_id returning *;
$$;
grant execute on function public.list_open_todos() to service_role;
grant execute on function public.mark_todo_done(bigint) to service_role;
이 스키마는 RLS 설정을 생략합니다. 왜냐하면 이 글에서는 Node.js에서 secret 키를 사용해 RLS를 우회하기 때문입니다. 브라우저나 모바일 클라이언트에서는 publishable 키를 사용해야 합니다. publishable 키는 테이블에 RLS를 적용했을 때 이를 준수합니다.
list_open_todos는 RPC 엔드포인트로 노출되는 Postgres 함수입니다.
Postgres 함수는 기본적으로 **security invoker**이며, 호출자의 권한으로 실행돼 RLS가 적용됩니다. **security definer**는 함수 소유자(보통 권한이 높은 DB 역할)로 실행되므로 호출자와는 별개로 동작합니다.
API 키와 연결
프로젝트 Overview 탭에서 다음 정보를 찾을 수 있습니다:
-
Project URL (
https://<project>.supabase.co) —SUPABASE_URL에 사용 -
Publishable key (
sb_publishable_..., 이전 이름anon) — 브라우저·모바일 앱에서 RLS가 적용되도록 할 때 사용; 이 글에서는 사용하지 않음
Node.js 백엔드에서는 Project Settings → API Keys에 있는 secret 키를 사용합니다:
- Secret key(s) (
sb_secret_...) — 전체 데이터 접근 권한, RLS를 우회함; 신뢰할 수 있는 서버에서만 사용하고 절대 클라이언트 번들이나 공개 레포에 포함하지 마세요. 기존의service_role키를 대체합니다.- 대시보드에서 secret 키를 확인하거나 새로 만들 수 있습니다(레거시 프로젝트: Legacy anon, service_role API keys 탭 →
service_role).
- 대시보드에서 secret 키를 확인하거나 새로 만들 수 있습니다(레거시 프로젝트: Legacy anon, service_role API keys 탭 →
Supabase JS 클라이언트는 PostgREST와 통신하며, 테이블(from)과 등록된 함수(rpc)를 존재한 이후에만 호출할 수 있습니다. DDL(create table, create function, grant 등)은 실행하지 않으므로, 스키마는 SQL Editor(또는 대규모 프로젝트에서는 Supabase CLI 마이그레이션)를 통해 적용해야 합니다.
환경 변수에 값을 저장합니다:
SUPABASE_URL=https://<project>.supabase.co
SUPABASE_SECRET_KEY=your-secret-key
클라이언트 설정
프로젝트 URL과 secret 키를 사용해 클라이언트를 생성합니다. 실제 앱에서는 환경 변수에서 값을 읽어오고, secret 키를 절대 커밋하지 않도록 주의하세요.
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_SECRET_KEY
);
public 스키마에 만든 모든 테이블은 supabase.from('')으로 접근할 수 있습니다. 커스텀 SQL 함수는 supabase.rpc('')으로 호출합니다.
데이터 삽입
한 번에 여러 행을 삽입하고, .select()를 사용해 생성된 레코드를 반환받습니다.
const { data, error } = await supabase
.from('todos')
.insert([
{ title: 'Learn Supabase client', done: false },
{ title: 'Run RPC example', done: false },
{ title: 'Ship demo', done: true },
])
.select();
if (error) {
throw new Error(error.message);
}
console.log(data);
읽기·수정·삭제
Select – 필터와 정렬
const { data, error } = await supabase
.from('todos')
.select('*')
.eq('done', false)
.order('id');
Update – 일치하는 행 수정
const { data, error } = await supabase
.from('todos')
.update({ done: true })
.eq('id', 1)
.select();
Delete – 일치하는 행 삭제
const { error } = await supabase
.from('todos')
.delete()
.eq('id', 1);
.eq(), .in(), .limit() 등 필터 헬퍼를 모든 작업에서 동일하게 체이닝할 수 있습니다.
RPC
데이터베이스 함수를 이름으로 호출합니다. list_open_todos()는 앱에서 필터 로직을 중복하지 않고 열려 있는 todo 목록을 반환합니다.
const { data, error } = await supabase.rpc('list_open_todos');
if (error) {
throw new Error(error.message);
}
console.log(data);
파라미터가 있는 함수는 두 번째 인자로 전달합니다:
await supabase.rpc('mark_todo_done', { todo_id: 42 });
SQL에서 파라미터(todo_id bigint)를 정의하고, secret‑key 백엔드에서 RPC를 호출할 때 service_role에 execute 권한을 부여해야 합니다.
그 외 중요한 사항
-
Row Level Security (RLS) – 테이블에 RLS를 활성화하면 정책에 의해 허용되기 전까지 Postgres가 접근을 차단합니다. publishable 키는 RLS의 영향을 받지만, secret 키는 받지 않습니다. RLS를 켤 때는 역할(
anon,authenticated)과 작업별로 정책을 추가하세요. -
Auth – Supabase Auth는
auth.users에 사용자를 저장합니다. 로그인 후 클라이언트 JWT에authenticated역할이 포함되며, 정책에서는auth.uid()를 사용해 사용자별 행을 제한할 수 있습니다. -
Migrations – 대시보드만으로 프로덕션을 수정하지 마세요. SQL을 버전 관리된 마이그레이션 파일에 두고, Supabase CLI의
supabase db push(연결된 프로젝트) 등을 이용해 적용하면 환경 간 일관성을 유지할 수 있습니다. -
Generated types –
supabase gen types typescript --project-id <id>명령으로 스키마 기반 TypeScript 타입을 생성해 타입 안전한 클라이언트를 만들 수 있습니다. -
Storage – S3 호환 버킷을 사용해 파일(이미지, PDF 등)을 저장합니다. 버킷 정책은 RLS와 유사하게 동작합니다.
-
Realtime – 테이블이나 채널에 대한
insert/update/delete를 구독해 실시간 UI 업데이트를 구현합니다. -
Edge Functions – Deno 기반 함수로 웹훅, 가벼운 API 로직, 혹은 데이터베이스에 두기 부적절한 작업을 처리합니다.
-
Local dev –
supabase init와supabase start로 로컬 Postgres와 서비스들을 띄울 수 있어 오프라인 작업에
