description: "Supabase 사용 시 패턴 (PostgreSQL + Supabase SDK). Prisma 대신 Supabase를 사용하는 프로젝트에 적용." paths:
Prisma 대신 Supabase를 사용할 때 적용한다.
서버와 클라이언트에서 사용하는 키가 다르다.
// 서버 (API Route, Server Component, NestJS 등)
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!, // RLS 우회 가능, 서버에서만 사용
)
// 클라이언트 컴포넌트 (브라우저)
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, // RLS 정책 적용됨
)
SERVICE_ROLE_KEY 는 절대 클라이언트에 노출 금지// 단건
const { data, error } = await supabase
.from('users')
.select('id, email, name')
.eq('id', userId)
.single()
// 목록 + 관계
const { data, error } = await supabase
.from('posts')
.select('*, author:users(id, name), tags(*)')
.eq('status', 'published')
.order('created_at', { ascending: false })
.range(0, 19) // 페이지네이션
if (error) throw new Error(error.message)
// 삽입 후 결과 반환
const { data, error } = await supabase
.from('posts')
.insert({ title, content, author_id: userId })
.select()
.single()
// 수정
const { data, error } = await supabase
.from('posts')
.update({ title, updated_at: new Date().toISOString() })
.eq('id', postId)
.select()
.single()
// 삭제
const { error } = await supabase
.from('posts')
.delete()
.eq('id', postId)
// Upsert
const { data, error } = await supabase
.from('profiles')
.upsert({ id: userId, bio }, { onConflict: 'id' })
.select()
.single()
모든 Supabase 쿼리는 error를 반드시 체크한다.
const { data, error } = await supabase.from('users').select()
if (error) throw new Error(error.message)
// 이후 data는 null이 아님이 보장됨
PGRST 에러코드로 세부 처리가 필요한 경우:
if (error) {
if (error.code === 'PGRST116') throw new NotFoundException('리소스를 찾을 수 없어요')
throw new Error(error.message)
}
-- 예시: 본인 데이터만 조회 허용
CREATE POLICY "Users can view own data"
ON users FOR SELECT
USING (auth.uid() = id);
service_role 키 → RLS 우회 (전체 접근)anon 키 → RLS 정책 적용// 업로드
const { data, error } = await supabase.storage
.from('avatars')
.upload(`${userId}/avatar.png`, file, {
upsert: true,
contentType: 'image/png',
})
// 공개 URL 조회
const { data } = supabase.storage
.from('avatars')
.getPublicUrl(`${userId}/avatar.png`)
const url = data.publicUrl
createSignedUrl 로 임시 접근 URL 발급const channel = supabase
.channel('room-updates')
.on('postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'messages' },
(payload) => {
setMessages((prev) => [...prev, payload.new as Message])
}
)
.subscribe()
// 컴포넌트 언마운트 시 구독 해제
return () => { supabase.removeChannel(channel) }
Supabase CLI로 DB 스키마에서 TypeScript 타입 자동 생성.
supabase gen types typescript --project-id <project-id> > src/types/supabase.ts
생성된 타입 활용:
import type { Database } from '@/types/supabase'
type User = Database['public']['Tables']['users']['Row']
type UserInsert = Database['public']['Tables']['users']['Insert']
아직 피드백이 없어요. 첫 번째로 의견을 남겨보세요!