
Supabase 架构
Supabase 在 PostgreSQL 之上封装了实时订阅、REST/GraphQL API、认证、存储和边缘函数——全部开源。

设置
npm install @supabase/supabase-js
import { createClient } from '@supabase/supabase-js'
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
行级安全(RLS)
-- 启用 RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- 策略:用户只能看到自己的帖子
CREATE POLICY "Users see own posts" ON posts
FOR SELECT USING (auth.uid() = user_id);
-- 策略:用户可以创建帖子
CREATE POLICY "Users create posts" ON posts
FOR INSERT WITH CHECK (auth.uid() = user_id);
-- 策略:用户可以更新自己的帖子
CREATE POLICY "Users update own posts" ON posts
FOR UPDATE USING (auth.uid() = user_id);
-- 策略:公开帖子对所有人可见
CREATE POLICY "Public posts visible" ON posts
FOR SELECT USING (published = true);

认证
// 邮箱/密码
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'secure-password',
options: {
data: { display_name: 'Alice' },
},
})
// 社交 OAuth
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: { redirectTo: `${window.location.origin}/auth/callback` },
})
// 获取当前用户
const { data: { user } } = await supabase.auth.getUser()
// 认证状态监听器
supabase.auth.onAuthStateChange((event, session) => {
if (event === 'SIGNED_IN') {
setUser(session?.user ?? null)
} else if (event === 'SIGNED_OUT') {
setUser(null)
}
})
数据库查询
// 类型安全,使用生成的类型
type Database = {
public: {
Tables: {
posts: {
Row: { id: string; title: string; content: string; user_id: string }
Insert: { title: string; content: string }
Update: Partial<{ title: string; content: string }>
}
}
}
}
const supabase = createClient<Database>(URL, KEY)
// CRUD 操作
const { data: posts, error } = await supabase
.from('posts')
.select(`
id,
title,
content,
created_at,
user:users(id, name, avatar_url)
`)
.eq('published', true)
.order('created_at', { ascending: false })
.range(0, 19)
// 插入
const { data: post } = await supabase
.from('posts')
.insert({ title: 'Hello', content: 'World' })
.select()
.single()
// 更新
const { data } = await supabase
.from('posts')
.update({ title: 'Updated' })
.eq('id', postId)
.select()
.single()

实时订阅
// 订阅表的所有变更
const channel = supabase
.channel('posts-changes')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: 'posts' },
(payload) => {
console.log('Change:', payload.eventType, payload.new)
}
)
.subscribe()
// 订阅特定行
const channel = supabase
.channel('my-post')
.on(
'postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'posts',
filter: `id=eq.${postId}`,
},
(payload) => setPost(payload.new)
)
.subscribe()
// 清理
return () => supabase.removeChannel(channel)
存储
// 上传文件
const { data, error } = await supabase.storage
.from('avatars')
.upload(`${userId}/avatar.jpg`, file, {
cacheControl: '3600',
upsert: true,
contentType: 'image/jpeg',
})
// 获取公开 URL
const { data: { publicUrl } } = supabase.storage
.from('avatars')
.getPublicUrl(`${userId}/avatar.jpg`)
// 签名 URL(用于私有存储桶)
const { data: { signedUrl } } = await supabase.storage
.from('private-docs')
.createSignedUrl('document.pdf', 3600) // 1 小时过期
边缘函数
// supabase/functions/send-welcome/index.ts
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts'
serve(async (req) => {
const { email, name } = await req.json()
await sendEmail({ to: email, subject: 'Welcome!', body: `Hello ${name}` })
return new Response(JSON.stringify({ sent: true }), {
headers: { 'Content-Type': 'application/json' },
})
})
supabase functions deploy send-welcome
supabase functions invoke send-welcome --body '{"email":"user@example.com","name":"Alice"}'