正在加载,请稍候…

Supabase:拥有超能力的 PostgreSQL — 认证、实时订阅与边缘函数

使用 Supabase 构建全栈应用——行级安全、实时订阅、认证提供商、存储、边缘函数及 JavaScript 客户端库。

Supabase:拥有超能力的 PostgreSQL — 认证、实时订阅与边缘函数

Supabase 架构

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

Supabase:拥有超能力的 PostgreSQL — 认证、实时订阅与边缘函数 插图

设置

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);

Supabase:拥有超能力的 PostgreSQL — 认证、实时订阅与边缘函数 插图

认证

// 邮箱/密码
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()

Supabase:拥有超能力的 PostgreSQL — 认证、实时订阅与边缘函数 插图

实时订阅

// 订阅表的所有变更
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"}'