supabase-mcp-integration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Supabase MCP Integration

Supabase MCP 集成

A comprehensive skill for building production-ready applications using Supabase - the open-source Backend-as-a-Service platform built on PostgreSQL. This skill covers authentication, database operations, real-time subscriptions, storage, TypeScript integration, and Row-Level Security patterns.
本技能全面介绍如何使用Supabase构建生产级应用——Supabase是基于PostgreSQL的开源后端即服务(Backend-as-a-Service)平台。内容涵盖身份验证、数据库操作、实时订阅、存储、TypeScript集成以及行级安全(Row-Level Security)模式。

When to Use This Skill

适用场景

Use this skill when:
  • Building full-stack web or mobile applications with PostgreSQL backend
  • Implementing authentication (email, OAuth, magic links, MFA) and session management
  • Creating real-time applications (chat, collaboration, live dashboards)
  • Managing file storage with image optimization and CDN delivery
  • Building multi-tenant SaaS applications with fine-grained authorization
  • Migrating from Firebase to SQL-based backend
  • Requiring type-safe database operations with TypeScript
  • Implementing Row-Level Security (RLS) for database authorization
  • Building applications with complex queries, joins, and relationships
  • Setting up instant REST/GraphQL APIs from database schema
在以下场景中使用本技能:
  • 构建基于PostgreSQL后端的全栈Web或移动应用
  • 实现身份验证(邮箱、OAuth、魔法链接、多因素认证)和会话管理
  • 创建实时应用(聊天、协作、实时仪表盘)
  • 管理文件存储,支持图片优化和CDN分发
  • 构建具备细粒度授权的多租户SaaS应用
  • 从Firebase迁移到基于SQL的后端
  • 需要通过TypeScript实现类型安全的数据库操作
  • 为数据库授权实现行级安全(RLS)
  • 构建包含复杂查询、关联和关系的应用
  • 从数据库架构自动生成即时REST/GraphQL API

Core Concepts

核心概念

Supabase Platform Architecture

Supabase平台架构

Supabase is an integrated platform built on enterprise-grade open-source components:
Key Components:
  • PostgreSQL Database: Full Postgres with extensions (PostGIS, pg_vector)
  • GoTrue (Auth): JWT-based authentication with multiple providers
  • PostgREST: Auto-generated REST APIs from database schema
  • Realtime: WebSocket server for database changes, broadcast, and presence
  • Storage: S3-compatible file storage with CDN and image optimization
  • Edge Functions: Globally distributed serverless functions (Deno runtime)
Unified Client Library:
typescript
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)

// All features through single client
await supabase.auth.signIn()           // Authentication
await supabase.from('users').select()  // Database
supabase.channel('room').subscribe()   // Realtime
await supabase.storage.from().upload() // Storage
Supabase是基于企业级开源组件构建的集成平台:
核心组件:
  • PostgreSQL数据库: 完整的PostgreSQL,支持扩展(PostGIS、pg_vector)
  • GoTrue(身份验证): 基于JWT的身份验证,支持多种提供商
  • PostgREST: 从数据库架构自动生成REST API
  • Realtime: 用于数据库变更、广播和在线状态追踪的WebSocket服务器
  • Storage: 兼容S3的文件存储,支持CDN和图片优化
  • Edge Functions: 全球分布式无服务器函数(Deno运行时)
统一客户端库:
typescript
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)

// 单一客户端调用所有功能
await supabase.auth.signIn()           // 身份验证
await supabase.from('users').select()  // 数据库操作
supabase.channel('room').subscribe()   // 实时功能
await supabase.storage.from().upload() // 存储操作

Row-Level Security (RLS)

行级安全(RLS)

Database-level authorization using PostgreSQL policies:
  • Define access rules directly in the database
  • Automatic enforcement on all queries
  • Integrated with JWT authentication
  • Fine-grained control at row and column level
基于PostgreSQL策略的数据库级授权:
  • 直接在数据库中定义访问规则
  • 自动强制执行所有查询
  • 与JWT身份验证集成
  • 支持行和列级的细粒度控制

JWT-Based Authentication

基于JWT的身份验证

Supabase Auth uses JSON Web Tokens:
  • Issued upon successful authentication
  • Automatically included in database queries
  • Used for RLS policy evaluation
  • Refresh token flow for long sessions
Supabase身份验证使用JSON Web令牌:
  • 身份验证成功后颁发令牌
  • 自动包含在数据库查询中
  • 用于RLS策略评估
  • 支持刷新令牌以维持长会话

Type Safety

类型安全

Automatic TypeScript type generation from database schema:
  • Generate types from live database
  • Type-safe queries and mutations
  • Compile-time error detection
  • IDE autocomplete support
从数据库架构自动生成TypeScript类型:
  • 从实时数据库生成类型
  • 类型安全的查询和变更
  • 编译时错误检测
  • IDE自动补全支持

Supabase Client Setup

Supabase客户端设置

Installation

安装

bash
undefined
bash
undefined

npm

npm

npm install @supabase/supabase-js
npm install @supabase/supabase-js

yarn

yarn

yarn add @supabase/supabase-js
yarn add @supabase/supabase-js

pnpm

pnpm

pnpm add @supabase/supabase-js
pnpm add @supabase/supabase-js

bun

bun

bun add @supabase/supabase-js
undefined
bun add @supabase/supabase-js
undefined

Environment Configuration

环境配置

bash
undefined
bash
undefined

.env.local

.env.local

NEXT_PUBLIC_SUPABASE_URL=https://xyzcompany.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
NEXT_PUBLIC_SUPABASE_URL=https://xyzcompany.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

For server-side operations (keep secure!)

服务端操作使用(请妥善保管!)

SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

**Security Note:** Never expose the `service_role` key in client-side code.
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

**安全注意:** 切勿在客户端代码中暴露`service_role`密钥。

Client Initialization Pattern (Recommended)

推荐的客户端初始化模式

typescript
// lib/supabase.ts

import { createClient, SupabaseClient } from '@supabase/supabase-js'
import { Database } from './database.types'

function validateEnvironment() {
  const url = process.env.NEXT_PUBLIC_SUPABASE_URL
  const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY

  if (!url) {
    throw new Error('Missing environment variable: NEXT_PUBLIC_SUPABASE_URL')
  }

  if (!anonKey) {
    throw new Error('Missing environment variable: NEXT_PUBLIC_SUPABASE_ANON_KEY')
  }

  return { url, anonKey }
}

let supabaseInstance: SupabaseClient<Database> | null = null

export function getSupabaseClient(): SupabaseClient<Database> {
  if (!supabaseInstance) {
    const { url, anonKey } = validateEnvironment()

    supabaseInstance = createClient<Database>(url, anonKey, {
      auth: {
        autoRefreshToken: true,
        persistSession: true,
        detectSessionInUrl: true
      },
      global: {
        headers: {
          'X-Application-Name': 'MyApp'
        }
      }
    })
  }

  return supabaseInstance
}

// Export singleton instance
export const supabase = getSupabaseClient()
typescript
// lib/supabase.ts

import { createClient, SupabaseClient } from '@supabase/supabase-js'
import { Database } from './database.types'

function validateEnvironment() {
  const url = process.env.NEXT_PUBLIC_SUPABASE_URL
  const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY

  if (!url) {
    throw new Error('缺少环境变量: NEXT_PUBLIC_SUPABASE_URL')
  }

  if (!anonKey) {
    throw new Error('缺少环境变量: NEXT_PUBLIC_SUPABASE_ANON_KEY')
  }

  return { url, anonKey }
}

let supabaseInstance: SupabaseClient<Database> | null = null

export function getSupabaseClient(): SupabaseClient<Database> {
  if (!supabaseInstance) {
    const { url, anonKey } = validateEnvironment()

    supabaseInstance = createClient<Database>(url, anonKey, {
      auth: {
        autoRefreshToken: true,
        persistSession: true,
        detectSessionInUrl: true
      },
      global: {
        headers: {
          'X-Application-Name': 'MyApp'
        }
      }
    })
  }

  return supabaseInstance
}

// 导出单例实例
export const supabase = getSupabaseClient()

Configuration Options

配置选项

typescript
const options = {
  // Database configuration
  db: {
    schema: 'public'  // Default schema
  },

  // Authentication configuration
  auth: {
    autoRefreshToken: true,     // Automatically refresh tokens
    persistSession: true,        // Persist session to localStorage
    detectSessionInUrl: true,    // Detect session from URL hash
    flowType: 'pkce',           // Use PKCE flow for OAuth
    storage: customStorage,      // Custom storage implementation
    storageKey: 'sb-auth-token' // Storage key for session
  },

  // Global configuration
  global: {
    headers: {
      'X-Application-Name': 'my-app',
      'apikey': SUPABASE_ANON_KEY
    },
    fetch: customFetch  // Custom fetch implementation
  },

  // Realtime configuration
  realtime: {
    params: {
      eventsPerSecond: 10
    },
    timeout: 10000,
    heartbeatInterval: 30000
  }
}

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, options)
typescript
const options = {
  // 数据库配置
  db: {
    schema: 'public'  // 默认架构
  },

  // 身份验证配置
  auth: {
    autoRefreshToken: true,     // 自动刷新令牌
    persistSession: true,        // 将会话持久化到localStorage
    detectSessionInUrl: true,    // 从URL哈希中检测会话
    flowType: 'pkce',           // 对OAuth使用PKCE流程
    storage: customStorage,      // 自定义存储实现
    storageKey: 'sb-auth-token' // 会话存储密钥
  },

  // 全局配置
  global: {
    headers: {
      'X-Application-Name': 'my-app',
      'apikey': SUPABASE_ANON_KEY
    },
    fetch: customFetch  // 自定义fetch实现
  },

  // 实时配置
  realtime: {
    params: {
      eventsPerSecond: 10
    },
    timeout: 10000,
    heartbeatInterval: 30000
  }
}

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, options)

Platform-Specific Setup

平台特定设置

React Native with AsyncStorage:
typescript
import AsyncStorage from '@react-native-async-storage/async-storage'
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
  auth: {
    storage: AsyncStorage,
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: false
  }
})
React Native with Expo SecureStore:
typescript
import * as SecureStore from 'expo-secure-store'
import { createClient } from '@supabase/supabase-js'

const ExpoSecureStoreAdapter = {
  getItem: (key: string) => SecureStore.getItemAsync(key),
  setItem: (key: string, value: string) => SecureStore.setItemAsync(key, value),
  removeItem: (key: string) => SecureStore.deleteItemAsync(key)
}

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
  auth: {
    storage: ExpoSecureStoreAdapter,
    autoRefreshToken: true,
    persistSession: true
  }
})
React Native 结合 AsyncStorage:
typescript
import AsyncStorage from '@react-native-async-storage/async-storage'
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
  auth: {
    storage: AsyncStorage,
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: false
  }
})
React Native 结合 Expo SecureStore:
typescript
import * as SecureStore from 'expo-secure-store'
import { createClient } from '@supabase/supabase-js'

const ExpoSecureStoreAdapter = {
  getItem: (key: string) => SecureStore.getItemAsync(key),
  setItem: (key: string, value: string) => SecureStore.setItemAsync(key, value),
  removeItem: (key: string) => SecureStore.deleteItemAsync(key)
}

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
  auth: {
    storage: ExpoSecureStoreAdapter,
    autoRefreshToken: true,
    persistSession: true
  }
})

Authentication & Authorization

身份验证与授权

Email/Password Authentication

邮箱/密码身份验证

Sign Up:
typescript
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'secure-password',
  options: {
    data: {
      // Additional user metadata
      display_name: 'John Doe',
      avatar_url: 'https://example.com/avatar.jpg'
    },
    emailRedirectTo: 'https://yourapp.com/welcome'
  }
})

if (error) {
  console.error('Signup failed:', error.message)
  return
}

console.log('User created:', data.user)
console.log('Session:', data.session)
Sign In:
typescript
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'secure-password'
})

if (error) {
  console.error('Login failed:', error.message)
  return
}

console.log('User:', data.user)
console.log('Session token:', data.session?.access_token)
注册:
typescript
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'secure-password',
  options: {
    data: {
      // 附加用户元数据
      display_name: 'John Doe',
      avatar_url: 'https://example.com/avatar.jpg'
    },
    emailRedirectTo: 'https://yourapp.com/welcome'
  }
})

if (error) {
  console.error('注册失败:', error.message)
  return
}

console.log('用户已创建:', data.user)
console.log('会话:', data.session)
登录:
typescript
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'secure-password'
})

if (error) {
  console.error('登录失败:', error.message)
  return
}

console.log('用户:', data.user)
console.log('会话令牌:', data.session?.access_token)

Magic Link (Passwordless)

魔法链接(无密码登录)

typescript
const { data, error } = await supabase.auth.signInWithOtp({
  email: 'user@example.com',
  options: {
    emailRedirectTo: 'https://yourapp.com/login',
    shouldCreateUser: true
  }
})

if (error) {
  console.error('Failed to send magic link:', error.message)
  return
}

console.log('Magic link sent')
typescript
const { data, error } = await supabase.auth.signInWithOtp({
  email: 'user@example.com',
  options: {
    emailRedirectTo: 'https://yourapp.com/login',
    shouldCreateUser: true
  }
})

if (error) {
  console.error('发送魔法链接失败:', error.message)
  return
}

console.log('魔法链接已发送')

One-Time Password (OTP) - Phone

一次性密码(OTP)- 手机号

typescript
// Send OTP
const { data, error } = await supabase.auth.signInWithOtp({
  phone: '+1234567890',
  options: {
    channel: 'sms' // or 'whatsapp'
  }
})

// Verify OTP
const { data: verifyData, error: verifyError } = await supabase.auth.verifyOtp({
  phone: '+1234567890',
  token: '123456',
  type: 'sms'
})
typescript
// 发送OTP
const { data, error } = await supabase.auth.signInWithOtp({
  phone: '+1234567890',
  options: {
    channel: 'sms' // 或 'whatsapp'
  }
})

// 验证OTP
const { data: verifyData, error: verifyError } = await supabase.auth.verifyOtp({
  phone: '+1234567890',
  token: '123456',
  type: 'sms'
})

OAuth (Social Login)

OAuth(社交登录)

typescript
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: 'https://yourapp.com/auth/callback',
    scopes: 'email profile',
    queryParams: {
      access_type: 'offline',
      prompt: 'consent'
    }
  }
})

// Supported providers:
// apple, google, github, gitlab, bitbucket, discord, facebook,
// twitter, microsoft, linkedin, notion, slack, spotify, twitch, etc.
typescript
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: 'https://yourapp.com/auth/callback',
    scopes: 'email profile',
    queryParams: {
      access_type: 'offline',
      prompt: 'consent'
    }
  }
})

// 支持的提供商:
// apple, google, github, gitlab, bitbucket, discord, facebook,
// twitter, microsoft, linkedin, notion, slack, spotify, twitch等

Session Management

会话管理

typescript
// Get current session
const { data: { session }, error } = await supabase.auth.getSession()

if (session) {
  console.log('Access token:', session.access_token)
  console.log('User:', session.user)
  console.log('Expires at:', session.expires_at)
}

// Get current user
const { data: { user }, error } = await supabase.auth.getUser()

// Refresh session
const { data, error } = await supabase.auth.refreshSession()

// Sign out
const { error } = await supabase.auth.signOut()
typescript
// 获取当前会话
const { data: { session }, error } = await supabase.auth.getSession()

if (session) {
  console.log('访问令牌:', session.access_token)
  console.log('用户:', session.user)
  console.log('过期时间:', session.expires_at)
}

// 获取当前用户
const { data: { user }, error } = await supabase.auth.getUser()

// 刷新会话
const { data, error } = await supabase.auth.refreshSession()

// 登出
const { error } = await supabase.auth.signOut()

Auth State Changes

身份验证状态变更

typescript
const { data: { subscription } } = supabase.auth.onAuthStateChange(
  (event, session) => {
    console.log('Auth event:', event)

    switch (event) {
      case 'SIGNED_IN':
        console.log('User signed in:', session?.user)
        break

      case 'SIGNED_OUT':
        console.log('User signed out')
        break

      case 'TOKEN_REFRESHED':
        console.log('Token refreshed')
        break

      case 'USER_UPDATED':
        console.log('User updated:', session?.user)
        break

      case 'PASSWORD_RECOVERY':
        console.log('Password recovery initiated')
        break
    }
  }
)

// Cleanup
subscription.unsubscribe()
typescript
const { data: { subscription } } = supabase.auth.onAuthStateChange(
  (event, session) => {
    console.log('身份验证事件:', event)

    switch (event) {
      case 'SIGNED_IN':
        console.log('用户已登录:', session?.user)
        break

      case 'SIGNED_OUT':
        console.log('用户已登出')
        break

      case 'TOKEN_REFRESHED':
        console.log('令牌已刷新')
        break

      case 'USER_UPDATED':
        console.log('用户信息已更新:', session?.user)
        break

      case 'PASSWORD_RECOVERY':
        console.log('密码恢复已启动')
        break
    }
  }
)

// 清理资源
subscription.unsubscribe()

User Management

用户管理

typescript
// Update user
const { data, error } = await supabase.auth.updateUser({
  email: 'newemail@example.com',
  password: 'new-password',
  data: {
    display_name: 'New Name',
    avatar_url: 'https://example.com/new-avatar.jpg'
  }
})

// Reset password
const { data, error } = await supabase.auth.resetPasswordForEmail(
  'user@example.com',
  {
    redirectTo: 'https://yourapp.com/reset-password'
  }
)

// Update password after reset
const { data: updateData, error: updateError } = await supabase.auth.updateUser({
  password: 'new-secure-password'
})
typescript
// 更新用户信息
const { data, error } = await supabase.auth.updateUser({
  email: 'newemail@example.com',
  password: 'new-password',
  data: {
    display_name: 'New Name',
    avatar_url: 'https://example.com/new-avatar.jpg'
  }
})

// 重置密码
const { data, error } = await supabase.auth.resetPasswordForEmail(
  'user@example.com',
  {
    redirectTo: 'https://yourapp.com/reset-password'
  }
)

// 重置后更新密码
const { data: updateData, error: updateError } = await supabase.auth.updateUser({
  password: 'new-secure-password'
})

Multi-Factor Authentication (MFA)

多因素认证(MFA)

typescript
// Enroll MFA
const { data: enrollData, error: enrollError } = await supabase.auth.mfa.enroll({
  factorType: 'totp',
  friendlyName: 'My Phone'
})

// Verify enrollment
const { data: verifyData, error: verifyError } = await supabase.auth.mfa.verify({
  factorId: enrollData.id,
  code: '123456'
})

// Challenge (during sign-in)
const { data: challengeData, error: challengeError } = await supabase.auth.mfa.challenge({
  factorId: 'factor-id'
})

// Verify challenge
const { data, error } = await supabase.auth.mfa.verify({
  factorId: 'factor-id',
  challengeId: challengeData.id,
  code: '123456'
})
typescript
// 注册MFA
const { data: enrollData, error: enrollError } = await supabase.auth.mfa.enroll({
  factorType: 'totp',
  friendlyName: 'My Phone'
})

// 验证注册
const { data: verifyData, error: verifyError } = await supabase.auth.mfa.verify({
  factorId: enrollData.id,
  code: '123456'
})

// 挑战(登录时)
const { data: challengeData, error: challengeError } = await supabase.auth.mfa.challenge({
  factorId: 'factor-id'
})

// 验证挑战
const { data, error } = await supabase.auth.mfa.verify({
  factorId: 'factor-id',
  challengeId: challengeData.id,
  code: '123456'
})

Database Operations

数据库操作

SELECT Queries

SELECT 查询

Basic Select:
typescript
// Select all columns
const { data, error } = await supabase
  .from('users')
  .select()

// Select specific columns
const { data, error } = await supabase
  .from('users')
  .select('id, email, created_at')
Filtering:
typescript
// Equal
const { data } = await supabase
  .from('users')
  .select()
  .eq('status', 'active')

// Not equal
const { data } = await supabase
  .from('users')
  .select()
  .neq('role', 'admin')

// Greater than / Less than
const { data } = await supabase
  .from('products')
  .select()
  .gt('price', 100)
  .lte('stock', 10)

// In array
const { data } = await supabase
  .from('users')
  .select()
  .in('id', [1, 2, 3, 4, 5])

// Pattern matching
const { data } = await supabase
  .from('users')
  .select()
  .like('email', '%@gmail.com')

// Case-insensitive pattern matching
const { data } = await supabase
  .from('products')
  .select()
  .ilike('name', '%laptop%')

// Full text search
const { data } = await supabase
  .from('articles')
  .select()
  .textSearch('title', 'postgres database')

// Null checks
const { data } = await supabase
  .from('users')
  .select()
  .is('deleted_at', null)
Ordering and Pagination:
typescript
// Order by
const { data } = await supabase
  .from('posts')
  .select()
  .order('created_at', { ascending: false })

// Multiple ordering
const { data } = await supabase
  .from('users')
  .select()
  .order('last_name', { ascending: true })
  .order('first_name', { ascending: true })

// Limit results
const { data } = await supabase
  .from('posts')
  .select()
  .limit(10)

// Pagination with range
const { data } = await supabase
  .from('posts')
  .select()
  .range(0, 9)  // First 10 items (0-indexed)
Joins and Nested Queries:
typescript
// One-to-many relationship
const { data } = await supabase
  .from('users')
  .select(`
    id,
    email,
    posts (
      id,
      title,
      created_at
    )
  `)

// Many-to-many with junction table
const { data } = await supabase
  .from('users')
  .select(`
    id,
    email,
    user_roles (
      role:roles (
        id,
        name
      )
    )
  `)

// Nested filtering
const { data } = await supabase
  .from('users')
  .select(`
    id,
    email,
    posts!inner (
      id,
      title
    )
  `)
  .eq('posts.published', true)
Aggregation:
typescript
// Count
const { count, error } = await supabase
  .from('users')
  .select('*', { count: 'exact', head: true })

// Count with filtering
const { count } = await supabase
  .from('users')
  .select('*', { count: 'exact', head: true })
  .eq('status', 'active')
基础查询:
typescript
// 查询所有列
const { data, error } = await supabase
  .from('users')
  .select()

// 查询指定列
const { data, error } = await supabase
  .from('users')
  .select('id, email, created_at')
过滤条件:
typescript
// 等于
const { data } = await supabase
  .from('users')
  .select()
  .eq('status', 'active')

// 不等于
const { data } = await supabase
  .from('users')
  .select()
  .neq('role', 'admin')

// 大于 / 小于等于
const { data } = await supabase
  .from('products')
  .select()
  .gt('price', 100)
  .lte('stock', 10)

// 包含在数组中
const { data } = await supabase
  .from('users')
  .select()
  .in('id', [1, 2, 3, 4, 5])

// 模式匹配
const { data } = await supabase
  .from('users')
  .select()
  .like('email', '%@gmail.com')

// 不区分大小写的模式匹配
const { data } = await supabase
  .from('products')
  .select()
  .ilike('name', '%laptop%')

// 全文搜索
const { data } = await supabase
  .from('articles')
  .select()
  .textSearch('title', 'postgres database')

// 空值检查
const { data } = await supabase
  .from('users')
  .select()
  .is('deleted_at', null)
排序与分页:
typescript
// 排序
const { data } = await supabase
  .from('posts')
  .select()
  .order('created_at', { ascending: false })

// 多条件排序
const { data } = await supabase
  .from('users')
  .select()
  .order('last_name', { ascending: true })
  .order('first_name', { ascending: true })

// 限制结果数量
const { data } = await supabase
  .from('posts')
  .select()
  .limit(10)

// 范围分页
const { data } = await supabase
  .from('posts')
  .select()
  .range(0, 9)  // 前10条数据(从0开始索引)
关联查询与嵌套查询:
typescript
// 一对多关系
const { data } = await supabase
  .from('users')
  .select(`
    id,
    email,
    posts (
      id,
      title,
      created_at
    )
  `)

// 多对多关系(通过中间表)
const { data } = await supabase
  .from('users')
  .select(`
    id,
    email,
    user_roles (
      role:roles (
        id,
        name
      )
    )
  `)

// 嵌套过滤
const { data } = await supabase
  .from('users')
  .select(`
    id,
    email,
    posts!inner (
      id,
      title
    )
  `)
  .eq('posts.published', true)
聚合查询:
typescript
// 计数
const { count, error } = await supabase
  .from('users')
  .select('*', { count: 'exact', head: true })

// 带过滤条件的计数
const { count } = await supabase
  .from('users')
  .select('*', { count: 'exact', head: true })
  .eq('status', 'active')

INSERT Operations

INSERT 操作

typescript
// Insert single row
const { data, error } = await supabase
  .from('users')
  .insert({
    email: 'user@example.com',
    name: 'John Doe',
    age: 30
  })
  .select()  // Return inserted row

// Insert multiple rows
const { data, error } = await supabase
  .from('users')
  .insert([
    { email: 'user1@example.com', name: 'User One' },
    { email: 'user2@example.com', name: 'User Two' },
    { email: 'user3@example.com', name: 'User Three' }
  ])
  .select()

// Upsert (Insert or Update)
const { data, error } = await supabase
  .from('users')
  .upsert({
    id: 1,
    email: 'updated@example.com',
    name: 'Updated Name'
  }, {
    onConflict: 'id'  // Conflict column(s)
  })
  .select()
typescript
// 插入单行
const { data, error } = await supabase
  .from('users')
  .insert({
    email: 'user@example.com',
    name: 'John Doe',
    age: 30
  })
  .select()  // 返回插入的行

// 插入多行
const { data, error } = await supabase
  .from('users')
  .insert([
    { email: 'user1@example.com', name: 'User One' },
    { email: 'user2@example.com', name: 'User Two' },
    { email: 'user3@example.com', name: 'User Three' }
  ])
  .select()

// 插入或更新(Upsert)
const { data, error } = await supabase
  .from('users')
  .upsert({
    id: 1,
    email: 'updated@example.com',
    name: 'Updated Name'
  }, {
    onConflict: 'id'  // 冲突列
  })
  .select()

UPDATE Operations

UPDATE 操作

typescript
// Update with filter
const { data, error } = await supabase
  .from('users')
  .update({ status: 'inactive' })
  .eq('last_login', null)
  .select()

// Update single row by ID
const { data, error } = await supabase
  .from('users')
  .update({ name: 'New Name' })
  .eq('id', userId)
  .select()
  .single()

// Increment value
const { data, error } = await supabase
  .from('profiles')
  .update({ login_count: supabase.raw('login_count + 1') })
  .eq('id', userId)
typescript
// 带过滤条件的更新
const { data, error } = await supabase
  .from('users')
  .update({ status: 'inactive' })
  .eq('last_login', null)
  .select()

// 根据ID更新单行
const { data, error } = await supabase
  .from('users')
  .update({ name: 'New Name' })
  .eq('id', userId)
  .select()
  .single()

// 增量更新
const { data, error } = await supabase
  .from('profiles')
  .update({ login_count: supabase.raw('login_count + 1') })
  .eq('id', userId)

DELETE Operations

DELETE 操作

typescript
// Delete with filter
const { error } = await supabase
  .from('users')
  .delete()
  .eq('status', 'banned')

// Delete single row
const { error } = await supabase
  .from('posts')
  .delete()
  .eq('id', postId)

// Soft delete pattern
const { error } = await supabase
  .from('users')
  .update({ deleted_at: new Date().toISOString() })
  .eq('id', userId)
typescript
// 带过滤条件的删除
const { error } = await supabase
  .from('users')
  .delete()
  .eq('status', 'banned')

// 删除单行
const { error } = await supabase
  .from('posts')
  .delete()
  .eq('id', postId)

// 软删除模式
const { error } = await supabase
  .from('users')
  .update({ deleted_at: new Date().toISOString() })
  .eq('id', userId)

RPC (Remote Procedure Calls)

RPC(远程过程调用)

typescript
// Call function without parameters
const { data, error } = await supabase
  .rpc('get_user_count')

// Call function with parameters
const { data, error } = await supabase
  .rpc('calculate_discount', {
    product_id: 123,
    user_id: 456
  })
typescript
// 调用无参数函数
const { data, error } = await supabase
  .rpc('get_user_count')

// 调用带参数函数
const { data, error } = await supabase
  .rpc('calculate_discount', {
    product_id: 123,
    user_id: 456
  })

Realtime Subscriptions

实时订阅

Database Change Subscriptions

数据库变更订阅

typescript
// Listen to all changes
const channel = supabase
  .channel('db-changes')
  .on(
    'postgres_changes',
    {
      event: '*',          // All events: INSERT, UPDATE, DELETE
      schema: 'public',
      table: 'posts'
    },
    (payload) => {
      console.log('Change received:', payload)
      console.log('Event type:', payload.eventType)
      console.log('New data:', payload.new)
      console.log('Old data:', payload.old)
    }
  )
  .subscribe()

// Cleanup
channel.unsubscribe()
Listen to Specific Events:
typescript
// INSERT only
const insertChannel = supabase
  .channel('post-inserts')
  .on(
    'postgres_changes',
    {
      event: 'INSERT',
      schema: 'public',
      table: 'posts'
    },
    (payload) => {
      console.log('New post created:', payload.new)
    }
  )
  .subscribe()

// UPDATE only
const updateChannel = supabase
  .channel('post-updates')
  .on(
    'postgres_changes',
    {
      event: 'UPDATE',
      schema: 'public',
      table: 'posts'
    },
    (payload) => {
      console.log('Post updated:', payload.new)
    }
  )
  .subscribe()

// Filter changes for specific rows
const channel = supabase
  .channel('user-posts')
  .on(
    'postgres_changes',
    {
      event: '*',
      schema: 'public',
      table: 'posts',
      filter: `user_id=eq.${userId}`
    },
    (payload) => {
      console.log('User post changed:', payload)
    }
  )
  .subscribe()
typescript
// 监听所有变更
const channel = supabase
  .channel('db-changes')
  .on(
    'postgres_changes',
    {
      event: '*',          // 所有事件: INSERT, UPDATE, DELETE
      schema: 'public',
      table: 'posts'
    },
    (payload) => {
      console.log('收到变更:', payload)
      console.log('事件类型:', payload.eventType)
      console.log('新数据:', payload.new)
      console.log('旧数据:', payload.old)
    }
  )
  .subscribe()

// 清理资源
channel.unsubscribe()
监听特定事件:
typescript
// 仅监听INSERT事件
const insertChannel = supabase
  .channel('post-inserts')
  .on(
    'postgres_changes',
    {
      event: 'INSERT',
      schema: 'public',
      table: 'posts'
    },
    (payload) => {
      console.log('新帖子已创建:', payload.new)
    }
  )
  .subscribe()

// 仅监听UPDATE事件
const updateChannel = supabase
  .channel('post-updates')
  .on(
    'postgres_changes',
    {
      event: 'UPDATE',
      schema: 'public',
      table: 'posts'
    },
    (payload) => {
      console.log('帖子已更新:', payload.new)
    }
  )
  .subscribe()

// 过滤特定行的变更
const channel = supabase
  .channel('user-posts')
  .on(
    'postgres_changes',
    {
      event: '*',
      schema: 'public',
      table: 'posts',
      filter: `user_id=eq.${userId}`
    },
    (payload) => {
      console.log('用户帖子已变更:', payload)
    }
  )
  .subscribe()

Broadcast Messages

广播消息

typescript
// Send broadcast
const channel = supabase.channel('room-1')

channel.subscribe((status) => {
  if (status === 'SUBSCRIBED') {
    channel.send({
      type: 'broadcast',
      event: 'cursor-move',
      payload: { x: 100, y: 200, user: 'Alice' }
    })
  }
})

// Receive broadcast
const channel = supabase
  .channel('room-1')
  .on('broadcast', { event: 'cursor-move' }, (payload) => {
    console.log('Cursor moved:', payload)
  })
  .subscribe()
typescript
// 发送广播
const channel = supabase.channel('room-1')

channel.subscribe((status) => {
  if (status === 'SUBSCRIBED') {
    channel.send({
      type: 'broadcast',
      event: 'cursor-move',
      payload: { x: 100, y: 200, user: 'Alice' }
    })
  }
})

// 接收广播
const channel = supabase
  .channel('room-1')
  .on('broadcast', { event: 'cursor-move' }, (payload) => {
    console.log('光标已移动:', payload)
  })
  .subscribe()

Presence Tracking

在线状态追踪

typescript
// Track user presence
const channel = supabase.channel('online-users')

// Set initial state
channel.subscribe(async (status) => {
  if (status === 'SUBSCRIBED') {
    await channel.track({
      user: 'user-1',
      online_at: new Date().toISOString(),
      status: 'online'
    })
  }
})

// Listen to presence changes
channel.on('presence', { event: 'sync' }, () => {
  const state = channel.presenceState()
  console.log('Online users:', state)
})

channel.on('presence', { event: 'join' }, ({ newPresences }) => {
  console.log('Users joined:', newPresences)
})

channel.on('presence', { event: 'leave' }, ({ leftPresences }) => {
  console.log('Users left:', leftPresences)
})

// Cleanup
channel.unsubscribe()
typescript
// 追踪用户在线状态
const channel = supabase.channel('online-users')

// 设置初始状态
channel.subscribe(async (status) => {
  if (status === 'SUBSCRIBED') {
    await channel.track({
      user: 'user-1',
      online_at: new Date().toISOString(),
      status: 'online'
    })
  }
})

// 监听在线状态变更
channel.on('presence', { event: 'sync' }, () => {
  const state = channel.presenceState()
  console.log('在线用户:', state)
})

channel.on('presence', { event: 'join' }, ({ newPresences }) => {
  console.log('用户已加入:', newPresences)
})

channel.on('presence', { event: 'leave' }, ({ leftPresences }) => {
  console.log('用户已离开:', leftPresences)
})

// 清理资源
channel.unsubscribe()

Storage Operations

存储操作

Bucket Management

存储桶管理

typescript
// List buckets
const { data: buckets, error } = await supabase
  .storage
  .listBuckets()

// Create bucket
const { data, error } = await supabase
  .storage
  .createBucket('avatars', {
    public: false,           // Private bucket
    fileSizeLimit: 1048576,  // 1MB limit
    allowedMimeTypes: ['image/png', 'image/jpeg']
  })

// Update bucket
const { data, error } = await supabase
  .storage
  .updateBucket('avatars', {
    public: true
  })

// Delete bucket
const { data, error } = await supabase
  .storage
  .deleteBucket('avatars')
typescript
// 列出存储桶
const { data: buckets, error } = await supabase
  .storage
  .listBuckets()

// 创建存储桶
const { data, error } = await supabase
  .storage
  .createBucket('avatars', {
    public: false,           // 私有存储桶
    fileSizeLimit: 1048576,  // 1MB大小限制
    allowedMimeTypes: ['image/png', 'image/jpeg']
  })

// 更新存储桶
const { data, error } = await supabase
  .storage
  .updateBucket('avatars', {
    public: true
  })

// 删除存储桶
const { data, error } = await supabase
  .storage
  .deleteBucket('avatars')

File Upload

文件上传

typescript
// Standard upload
const file = event.target.files[0]
const filePath = `${userId}/${Date.now()}-${file.name}`

const { data, error } = await supabase
  .storage
  .from('avatars')
  .upload(filePath, file, {
    cacheControl: '3600',
    upsert: false
  })

// Upload with progress tracking
const { data, error } = await supabase
  .storage
  .from('videos')
  .upload(filePath, file, {
    onUploadProgress: (progress) => {
      const percent = (progress.loaded / progress.total) * 100
      console.log(`Upload progress: ${percent.toFixed(2)}%`)
    }
  })
typescript
// 标准上传
const file = event.target.files[0]
const filePath = `${userId}/${Date.now()}-${file.name}`

const { data, error } = await supabase
  .storage
  .from('avatars')
  .upload(filePath, file, {
    cacheControl: '3600',
    upsert: false
  })

// 带进度追踪的上传
const { data, error } = await supabase
  .storage
  .from('videos')
  .upload(filePath, file, {
    onUploadProgress: (progress) => {
      const percent = (progress.loaded / progress.total) * 100
      console.log(`上传进度: ${percent.toFixed(2)}%`)
    }
  })

File Download and URLs

文件下载与URL获取

typescript
// Download file
const { data, error } = await supabase
  .storage
  .from('avatars')
  .download('path/to/file.jpg')

// Get public URL (for public buckets)
const { data } = supabase
  .storage
  .from('avatars')
  .getPublicUrl('path/to/file.jpg')

// Create signed URL (for private buckets)
const { data, error } = await supabase
  .storage
  .from('private-files')
  .createSignedUrl('path/to/file.pdf', 60) // Expires in 60 seconds
typescript
// 下载文件
const { data, error } = await supabase
  .storage
  .from('avatars')
  .download('path/to/file.jpg')

// 获取公共URL(适用于公共存储桶)
const { data } = supabase
  .storage
  .from('avatars')
  .getPublicUrl('path/to/file.jpg')

// 创建签名URL(适用于私有存储桶)
const { data, error } = await supabase
  .storage
  .from('private-files')
  .createSignedUrl('path/to/file.pdf', 60) // 60秒后过期

Image Transformation

图片转换

typescript
const { data } = supabase
  .storage
  .from('avatars')
  .getPublicUrl('user-avatar.jpg', {
    transform: {
      width: 200,
      height: 200,
      resize: 'cover',  // or 'contain', 'fill'
      quality: 80,
      format: 'webp'
    }
  })
typescript
const { data } = supabase
  .storage
  .from('avatars')
  .getPublicUrl('user-avatar.jpg', {
    transform: {
      width: 200,
      height: 200,
      resize: 'cover',  // 或 'contain', 'fill'
      quality: 80,
      format: 'webp'
    }
  })

File Management

文件管理

typescript
// List files
const { data, error } = await supabase
  .storage
  .from('avatars')
  .list('user-123', {
    limit: 100,
    offset: 0,
    sortBy: { column: 'name', order: 'asc' }
  })

// Delete files
const { data, error } = await supabase
  .storage
  .from('avatars')
  .remove(['path/to/file1.jpg', 'path/to/file2.jpg'])

// Move file
const { data, error } = await supabase
  .storage
  .from('avatars')
  .move('old/path/file.jpg', 'new/path/file.jpg')

// Copy file
const { data, error } = await supabase
  .storage
  .from('avatars')
  .copy('source/file.jpg', 'destination/file.jpg')
typescript
// 列出文件
const { data, error } = await supabase
  .storage
  .from('avatars')
  .list('user-123', {
    limit: 100,
    offset: 0,
    sortBy: { column: 'name', order: 'asc' }
  })

// 删除文件
const { data, error } = await supabase
  .storage
  .from('avatars')
  .remove(['path/to/file1.jpg', 'path/to/file2.jpg'])

// 移动文件
const { data, error } = await supabase
  .storage
  .from('avatars')
  .move('old/path/file.jpg', 'new/path/file.jpg')

// 复制文件
const { data, error } = await supabase
  .storage
  .from('avatars')
  .copy('source/file.jpg', 'destination/file.jpg')

TypeScript Integration

TypeScript集成

Generate Database Types

生成数据库类型

bash
undefined
bash
undefined

Install Supabase CLI

安装Supabase CLI

npm install -g supabase
npm install -g supabase

Login

登录

supabase login
supabase login

Generate types

生成类型

supabase gen types typescript --project-id YOUR_PROJECT_ID > database.types.ts
undefined
supabase gen types typescript --project-id YOUR_PROJECT_ID > database.types.ts
undefined

Use Generated Types

使用生成的类型

typescript
import { createClient } from '@supabase/supabase-js'
import { Database } from './database.types'

// Create typed client
const supabase = createClient<Database>(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
)

// Type-safe queries
const { data, error } = await supabase
  .from('users')
  .select('id, email, created_at')
  .eq('id', userId)

// data is typed as:
// Array<{ id: string; email: string; created_at: string }> | null

// Type-safe inserts
const { data, error } = await supabase
  .from('posts')
  .insert({
    title: 'My Post',
    content: 'Content here',
    user_id: userId,
    published: true
  })
  .select()
typescript
import { createClient } from '@supabase/supabase-js'
import { Database } from './database.types'

// 创建类型化客户端
const supabase = createClient<Database>(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
)

// 类型安全的查询
const { data, error } = await supabase
  .from('users')
  .select('id, email, created_at')
  .eq('id', userId)

// data的类型为:
// Array<{ id: string; email: string; created_at: string }> | null

// 类型安全的插入
const { data, error } = await supabase
  .from('posts')
  .insert({
    title: 'My Post',
    content: 'Content here',
    user_id: userId,
    published: true
  })
  .select()

Helper Types

辅助类型

typescript
import { Database } from './database.types'

// Get table row type
type User = Database['public']['Tables']['users']['Row']

// Get insert type
type NewUser = Database['public']['Tables']['users']['Insert']

// Get update type
type UserUpdate = Database['public']['Tables']['users']['Update']

// Get enum type
type UserRole = Database['public']['Enums']['user_role']

// Use in functions
function createUser(user: NewUser): Promise<User> {
  // Implementation
}
typescript
import { Database } from './database.types'

// 获取表行类型
type User = Database['public']['Tables']['users']['Row']

// 获取插入类型
type NewUser = Database['public']['Tables']['users']['Insert']

// 获取更新类型
type UserUpdate = Database['public']['Tables']['users']['Update']

// 获取枚举类型
type UserRole = Database['public']['Enums']['user_role']

// 在函数中使用
function createUser(user: NewUser): Promise<User> {
  // 实现逻辑
}

Row-Level Security (RLS)

行级安全(RLS)

Enable RLS

启用RLS

sql
-- Enable RLS on a table
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
sql
-- 在表上启用RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

Common RLS Patterns

常见RLS模式

Public Read Access:
sql
CREATE POLICY "Public profiles are visible to everyone"
ON profiles
FOR SELECT
TO anon, authenticated
USING (true);
User Can Only See Own Data:
sql
CREATE POLICY "Users can only see own data"
ON posts
FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
User Can Only Modify Own Data:
sql
CREATE POLICY "Users can insert own posts"
ON posts
FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);

CREATE POLICY "Users can update own posts"
ON posts
FOR UPDATE
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);

CREATE POLICY "Users can delete own posts"
ON posts
FOR DELETE
TO authenticated
USING (auth.uid() = user_id);
Multi-Tenant Pattern:
sql
CREATE POLICY "Users can only see data from own organization"
ON documents
FOR SELECT
TO authenticated
USING (
  organization_id IN (
    SELECT organization_id
    FROM user_organizations
    WHERE user_id = auth.uid()
  )
);
Role-Based Access Control:
sql
CREATE POLICY "Admin can see all users"
ON users
FOR SELECT
TO authenticated
USING (
  auth.jwt() ->> 'role' = 'admin'
  OR auth.uid() = id
);
公共读取权限:
sql
CREATE POLICY "公开资料对所有人可见"
ON profiles
FOR SELECT
TO anon, authenticated
USING (true);
用户仅能查看自己的数据:
sql
CREATE POLICY "用户仅能查看自己的数据"
ON posts
FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
用户仅能修改自己的数据:
sql
CREATE POLICY "用户仅能插入自己的帖子"
ON posts
FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);

CREATE POLICY "用户仅能更新自己的帖子"
ON posts
FOR UPDATE
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);

CREATE POLICY "用户仅能删除自己的帖子"
ON posts
FOR DELETE
TO authenticated
USING (auth.uid() = user_id);
多租户模式:
sql
CREATE POLICY "用户仅能查看自己组织的数据"
ON documents
FOR SELECT
TO authenticated
USING (
  organization_id IN (
    SELECT organization_id
    FROM user_organizations
    WHERE user_id = auth.uid()
  )
);
基于角色的访问控制:
sql
CREATE POLICY "管理员可以查看所有用户"
ON users
FOR SELECT
TO authenticated
USING (
  auth.jwt() ->> 'role' = 'admin'
  OR auth.uid() = id
);

Best Practices

最佳实践

Client Initialization

客户端初始化

Use Singleton Pattern:
  • Create single client instance and reuse across app
  • Avoid creating new clients on every request
  • Store in module-level variable or context
使用单例模式:
  • 创建单个客户端实例并在应用中复用
  • 避免在每次请求时创建新客户端
  • 存储在模块级变量或上下文

Error Handling

错误处理

Always Check Errors:
typescript
const { data, error } = await supabase
  .from('users')
  .select()

if (error) {
  console.error('Error fetching users:', error.message)
  // Handle error appropriately
  return
}

// Use data safely
console.log(data)
Use throwOnError() for Promise Rejection:
typescript
try {
  const { data } = await supabase
    .from('users')
    .insert({ name: 'John' })
    .throwOnError()

  console.log('User created:', data)
} catch (error) {
  console.error('Failed to create user:', error)
}
始终检查错误:
typescript
const { data, error } = await supabase
  .from('users')
  .select()

if (error) {
  console.error('获取用户失败:', error.message)
  // 适当处理错误
  return
}

// 安全使用数据
console.log(data)
使用throwOnError()触发Promise拒绝:
typescript
try {
  const { data } = await supabase
    .from('users')
    .insert({ name: 'John' })
    .throwOnError()

  console.log('用户已创建:', data)
} catch (error) {
  console.error('创建用户失败:', error)
}

Security

安全

Never Expose Service Role Key:
  • Use
    anon
    key in client-side code
  • Use
    service_role
    key only in server-side code
  • Keep service role key in server environment variables
Always Enable RLS:
  • Enable RLS on all tables
  • Create appropriate policies for each table
  • Test policies thoroughly
Validate User Input:
  • Never trust client-side data
  • Use database constraints and validations
  • Validate in both client and database
切勿暴露Service Role密钥:
  • 在客户端代码中使用
    anon
    密钥
  • 仅在服务端代码中使用
    service_role
    密钥
  • 将Service Role密钥存储在服务端环境变量中
始终启用RLS:
  • 在所有表上启用RLS
  • 为每个表创建适当的策略
  • 彻底测试策略
验证用户输入:
  • 切勿信任客户端数据
  • 使用数据库约束和验证
  • 在客户端和数据库两端进行验证

Performance

性能

Use Select Wisely:
typescript
// Bad: Fetch all columns
const { data } = await supabase.from('users').select()

// Good: Only fetch needed columns
const { data } = await supabase.from('users').select('id, email')
Use Pagination:
typescript
// Bad: Fetch all rows
const { data } = await supabase.from('posts').select()

// Good: Paginate results
const { data } = await supabase
  .from('posts')
  .select()
  .range(0, 9)
  .order('created_at', { ascending: false })
Index Database Columns:
  • Add indexes for frequently queried columns
  • Index foreign keys
  • Use composite indexes for multi-column queries
合理使用Select:
typescript
// 不良实践:获取所有列
const { data } = await supabase.from('users').select()

// 良好实践:仅获取需要的列
const { data } = await supabase.from('users').select('id, email')
使用分页:
typescript
// 不良实践:获取所有行
const { data } = await supabase.from('posts').select()

// 良好实践:分页获取结果
const { data } = await supabase
  .from('posts')
  .select()
  .range(0, 9)
  .order('created_at', { ascending: false })
为数据库列添加索引:
  • 为频繁查询的列添加索引
  • 为外键添加索引
  • 为多列查询使用复合索引

Connection Management

连接管理

Reuse Client Instance:
  • Don't create new client for each request
  • Use singleton pattern for client initialization
  • Consider connection pooling for server-side
Clean Up Subscriptions:
typescript
useEffect(() => {
  const channel = supabase.channel('room-1')

  channel.subscribe(/* ... */)

  return () => {
    channel.unsubscribe()
  }
}, [])
复用客户端实例:
  • 不为每个请求创建新客户端
  • 使用单例模式初始化客户端
  • 服务端考虑使用连接池
清理订阅:
typescript
useEffect(() => {
  const channel = supabase.channel('room-1')

  channel.subscribe(/* ... */)

  return () => {
    channel.unsubscribe()
  }
}, [])

Common Patterns & Workflows

常见模式与工作流

User Authentication Flow

用户身份验证流程

typescript
// 1. Sign up
const { data: signUpData, error: signUpError } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'secure-password'
})

// 2. Listen to auth state
supabase.auth.onAuthStateChange((event, session) => {
  if (event === 'SIGNED_IN') {
    // Redirect to dashboard
  }
})

// 3. Protected route check
const { data: { session } } = await supabase.auth.getSession()
if (!session) {
  // Redirect to login
}

// 4. Sign out
await supabase.auth.signOut()
typescript
// 1. 注册
const { data: signUpData, error: signUpError } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'secure-password'
})

// 2. 监听身份验证状态
supabase.auth.onAuthStateChange((event, session) => {
  if (event === 'SIGNED_IN') {
    // 重定向到仪表盘
  }
})

// 3. 受保护路由检查
const { data: { session } } = await supabase.auth.getSession()
if (!session) {
  // 重定向到登录页
}

// 4. 登出
await supabase.auth.signOut()

CRUD with RLS

结合RLS的CRUD操作

typescript
// Enable RLS on table
/*
ALTER TABLE todos ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users can CRUD own todos"
ON todos
FOR ALL
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
*/

// Create
const { data, error } = await supabase
  .from('todos')
  .insert({
    user_id: session.user.id,
    title: 'My Todo',
    completed: false
  })
  .select()

// Read (only user's own todos due to RLS)
const { data, error } = await supabase
  .from('todos')
  .select()

// Update
const { data, error } = await supabase
  .from('todos')
  .update({ completed: true })
  .eq('id', todoId)

// Delete
const { error } = await supabase
  .from('todos')
  .delete()
  .eq('id', todoId)
typescript
// 在表上启用RLS
/*
ALTER TABLE todos ENABLE ROW LEVEL SECURITY;

CREATE POLICY "用户可以CRUD自己的待办事项"
ON todos
FOR ALL
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
*/

// 创建
const { data, error } = await supabase
  .from('todos')
  .insert({
    user_id: session.user.id,
    title: 'My Todo',
    completed: false
  })
  .select()

// 读取(由于RLS,仅返回用户自己的待办事项)
const { data, error } = await supabase
  .from('todos')
  .select()

// 更新
const { data, error } = await supabase
  .from('todos')
  .update({ completed: true })
  .eq('id', todoId)

// 删除
const { error } = await supabase
  .from('todos')
  .delete()
  .eq('id', todoId)

Real-Time Chat Implementation

实时聊天实现

typescript
import { useEffect, useState } from 'react'
import { supabase } from '@/lib/supabase'

function Chat({ roomId, userId }) {
  const [messages, setMessages] = useState([])

  useEffect(() => {
    // Fetch existing messages
    const fetchMessages = async () => {
      const { data } = await supabase
        .from('messages')
        .select()
        .eq('room_id', roomId)
        .order('created_at', { ascending: true })

      if (data) setMessages(data)
    }

    fetchMessages()

    // Subscribe to new messages
    const channel = supabase
      .channel(`room-${roomId}`)
      .on(
        'postgres_changes',
        {
          event: 'INSERT',
          schema: 'public',
          table: 'messages',
          filter: `room_id=eq.${roomId}`
        },
        (payload) => {
          setMessages((prev) => [...prev, payload.new])
        }
      )
      .subscribe()

    return () => {
      channel.unsubscribe()
    }
  }, [roomId])

  const sendMessage = async (content: string) => {
    await supabase.from('messages').insert({
      room_id: roomId,
      user_id: userId,
      content
    })
  }

  return (
    <div>
      {messages.map((msg) => (
        <div key={msg.id}>{msg.content}</div>
      ))}
    </div>
  )
}
typescript
import { useEffect, useState } from 'react'
import { supabase } from '@/lib/supabase'

function Chat({ roomId, userId }) {
  const [messages, setMessages] = useState([])

  useEffect(() => {
    // 获取现有消息
    const fetchMessages = async () => {
      const { data } = await supabase
        .from('messages')
        .select()
        .eq('room_id', roomId)
        .order('created_at', { ascending: true })

      if (data) setMessages(data)
    }

    fetchMessages()

    // 订阅新消息
    const channel = supabase
      .channel(`room-${roomId}`)
      .on(
        'postgres_changes',
        {
          event: 'INSERT',
          schema: 'public',
          table: 'messages',
          filter: `room_id=eq.${roomId}`
        },
        (payload) => {
          setMessages((prev) => [...prev, payload.new])
        }
      )
      .subscribe()

    return () => {
      channel.unsubscribe()
    }
  }, [roomId])

  const sendMessage = async (content: string) => {
    await supabase.from('messages').insert({
      room_id: roomId,
      user_id: userId,
      content
    })
  }

  return (
    <div>
      {messages.map((msg) => (
        <div key={msg.id}>{msg.content}</div>
      ))}
    </div>
  )
}

File Upload with Progress

带进度的文件上传

typescript
async function uploadAvatar(file: File, userId: string) {
  const filePath = `${userId}/${Date.now()}-${file.name}`

  const { data, error } = await supabase.storage
    .from('avatars')
    .upload(filePath, file, {
      cacheControl: '3600',
      upsert: false,
      onUploadProgress: (progress) => {
        const percent = (progress.loaded / progress.total) * 100
        console.log(`Upload: ${percent.toFixed(2)}%`)
      }
    })

  if (error) {
    console.error('Upload failed:', error.message)
    return null
  }

  // Get public URL
  const { data: urlData } = supabase.storage
    .from('avatars')
    .getPublicUrl(filePath)

  // Update user profile with avatar URL
  await supabase
    .from('profiles')
    .update({ avatar_url: urlData.publicUrl })
    .eq('id', userId)

  return urlData.publicUrl
}
typescript
async function uploadAvatar(file: File, userId: string) {
  const filePath = `${userId}/${Date.now()}-${file.name}`

  const { data, error } = await supabase.storage
    .from('avatars')
    .upload(filePath, file, {
      cacheControl: '3600',
      upsert: false,
      onUploadProgress: (progress) => {
        const percent = (progress.loaded / progress.total) * 100
        console.log(`上传进度: ${percent.toFixed(2)}%`)
      }
    })

  if (error) {
    console.error('上传失败:', error.message)
    return null
  }

  // 获取公共URL
  const { data: urlData } = supabase.storage
    .from('avatars')
    .getPublicUrl(filePath)

  // 更新用户头像URL
  await supabase
    .from('profiles')
    .update({ avatar_url: urlData.publicUrl })
    .eq('id', userId)

  return urlData.publicUrl
}

Troubleshooting

故障排查

Common Issues

常见问题

RLS Blocking Queries:
  • Check if RLS is enabled:
    ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;
  • Verify policies exist and match your use case
  • Test policies with different user contexts
  • Use
    USING
    clause for SELECT/UPDATE/DELETE
  • Use
    WITH CHECK
    clause for INSERT/UPDATE
Auth Session Not Persisting:
  • Ensure
    persistSession: true
    in config
  • Check if storage (localStorage) is available
  • Verify cookies are not blocked
  • Check if third-party cookies are enabled (for OAuth)
Realtime Not Working:
  • Enable realtime on table in Supabase dashboard
  • Check if RLS policies allow subscriptions
  • Verify channel subscription is successful
  • Check network/firewall blocking WebSockets
Type Generation Errors:
  • Ensure Supabase CLI is installed and updated
  • Verify project ID is correct
  • Check network connectivity to Supabase
  • Try regenerating types with
    --debug
    flag
RLS阻止查询:
  • 检查是否已启用RLS:
    ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;
  • 验证策略是否存在并符合你的使用场景
  • 在不同用户上下文中测试策略
  • 对SELECT/UPDATE/DELETE使用
    USING
    子句
  • 对INSERT/UPDATE使用
    WITH CHECK
    子句
身份验证会话未持久化:
  • 确保配置中
    persistSession: true
  • 检查localStorage是否可用
  • 验证Cookie未被阻止
  • 检查第三方Cookie是否启用(针对OAuth)
实时功能不工作:
  • 在Supabase控制台中为表启用实时功能
  • 检查RLS策略是否允许订阅
  • 验证频道订阅是否成功
  • 检查网络/防火墙是否阻止WebSocket
类型生成错误:
  • 确保Supabase CLI已安装并更新
  • 验证项目ID是否正确
  • 检查与Supabase的网络连接
  • 尝试使用
    --debug
    标志重新生成类型

Debug RLS Policies

调试RLS策略

sql
-- Test policy as specific user
SET request.jwt.claims.sub = 'user-uuid-here';

-- Run query to see what's visible
SELECT * FROM posts;

-- Reset to admin
RESET request.jwt.claims.sub;
sql
// 以特定用户身份测试策略
SET request.jwt.claims.sub = 'user-uuid-here';

// 运行查询查看可见数据
SELECT * FROM posts;

// 重置为管理员
RESET request.jwt.claims.sub;

Performance Issues

性能问题

Slow Queries:
  • Add indexes on frequently queried columns
  • Use
    EXPLAIN ANALYZE
    to analyze query plan
  • Avoid fetching unnecessary columns
  • Use pagination for large datasets
Too Many Connections:
  • Use connection pooling
  • Reuse client instance
  • Close unused subscriptions
  • Consider using Edge Functions for server-side logic
查询缓慢:
  • 为频繁查询的列添加索引
  • 使用
    EXPLAIN ANALYZE
    分析查询计划
  • 避免获取不必要的列
  • 对大型数据集使用分页
连接数过多:
  • 使用连接池
  • 复用客户端实例
  • 关闭未使用的订阅
  • 考虑使用Edge Functions处理服务端逻辑

Production Deployment

生产环境部署

Environment Configuration

环境配置

bash
undefined
bash
undefined

Production .env

生产环境.env

NEXT_PUBLIC_SUPABASE_URL=https://prod-project.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=prod-anon-key SUPABASE_SERVICE_ROLE_KEY=prod-service-role-key
NEXT_PUBLIC_SUPABASE_URL=https://prod-project.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=prod-anon-key SUPABASE_SERVICE_ROLE_KEY=prod-service-role-key

Staging .env

预发布环境.env

NEXT_PUBLIC_SUPABASE_URL=https://staging-project.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=staging-anon-key SUPABASE_SERVICE_ROLE_KEY=staging-service-role-key
undefined
NEXT_PUBLIC_SUPABASE_URL=https://staging-project.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=staging-anon-key SUPABASE_SERVICE_ROLE_KEY=staging-service-role-key
undefined

Database Migrations

数据库迁移

Use Supabase CLI for schema migrations:
bash
undefined
使用Supabase CLI进行架构迁移:
bash
undefined

Initialize migrations

初始化迁移

supabase migration new create_posts_table
supabase migration new create_posts_table

Apply migrations

应用迁移

supabase db push
supabase db push

Generate migration from changes

根据变更生成迁移

supabase db diff -f create_users_table
undefined
supabase db diff -f create_users_table
undefined

Monitoring

监控

  • Enable Supabase Dashboard monitoring
  • Set up alerts for errors and performance issues
  • Monitor database connections
  • Track API usage and quotas
  • Set up logging for auth events
  • 启用Supabase控制台监控
  • 设置错误和性能问题警报
  • 监控数据库连接
  • 跟踪API使用情况和配额
  • 为身份验证事件设置日志

Backup Strategy

备份策略

  • Enable automatic backups in Supabase dashboard
  • Configure point-in-time recovery
  • Test restoration procedures
  • Export schema and data regularly
  • Store backups in separate location
  • 在Supabase控制台中启用自动备份
  • 配置点-in-time恢复
  • 测试恢复流程
  • 定期导出架构和数据
  • 将备份存储在单独位置

Scaling Considerations

扩展考虑

  • Upgrade Supabase plan for higher limits
  • Use database connection pooling
  • Implement caching (Redis, etc.)
  • Consider read replicas for heavy read loads
  • Use Edge Functions for heavy compute
  • Optimize database indexes
  • Monitor and optimize slow queries

Skill Version: 1.0.0 Last Updated: October 2025 Skill Category: Backend-as-a-Service, Database Integration, Authentication, Real-time Compatible With: React, Next.js, Vue, Angular, React Native, Flutter, Node.js, Deno
  • 升级Supabase计划以获取更高限制
  • 使用数据库连接池
  • 实现缓存(如Redis)
  • 对高读取负载使用只读副本
  • 使用Edge Functions处理繁重计算
  • 优化数据库索引
  • 监控并优化缓慢查询

技能版本: 1.0.0 最后更新: 2025年10月 技能分类: 后端即服务、数据库集成、身份验证、实时功能 兼容平台: React、Next.js、Vue、Angular、React Native、Flutter、Node.js、Deno