supabase-mcp-integration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSupabase 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() // StorageSupabase是基于企业级开源组件构建的集成平台:
核心组件:
- 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
undefinedbash
undefinednpm
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
undefinedbun add @supabase/supabase-js
undefinedEnvironment Configuration
环境配置
bash
undefinedbash
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 secondstypescript
// 下载文件
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
undefinedbash
undefinedInstall 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
undefinedsupabase gen types typescript --project-id YOUR_PROJECT_ID > database.types.ts
undefinedUse 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 key in client-side code
anon - Use key only in server-side code
service_role - 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 clause for SELECT/UPDATE/DELETE
USING - Use clause for INSERT/UPDATE
WITH CHECK
Auth Session Not Persisting:
- Ensure in config
persistSession: true - 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 flag
--debug
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 to analyze query plan
EXPLAIN ANALYZE - 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
undefinedbash
undefinedProduction .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
undefinedNEXT_PUBLIC_SUPABASE_URL=https://staging-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=staging-anon-key
SUPABASE_SERVICE_ROLE_KEY=staging-service-role-key
undefinedDatabase Migrations
数据库迁移
Use Supabase CLI for schema migrations:
bash
undefined使用Supabase CLI进行架构迁移:
bash
undefinedInitialize 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
undefinedsupabase db diff -f create_users_table
undefinedMonitoring
监控
- 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