supabase-backend-platform

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Supabase Backend Platform Skill

Supabase后端平台技能指南


progressive_disclosure: entry_point: summary: "Open-source Firebase alternative with Postgres, authentication, storage, and realtime" when_to_use: - "When building full-stack applications" - "When auth, database, and storage are required" - "When realtime subscriptions are needed" - "When using Next.js, React, or Vue" quick_start: - "Create project on Supabase console" - "npm install @supabase/supabase-js" - "Initialize client with URL and anon key" - "Use auth, database, storage, realtime" token_estimate: entry: 80-95 full: 5000-6000


progressive_disclosure: entry_point: summary: "开源Firebase替代方案,内置Postgres、身份验证、存储与实时功能" when_to_use: - "构建全栈应用时" - "需要身份验证、数据库和存储功能时" - "需要实时订阅服务时" - "使用Next.js、React或Vue开发时" quick_start: - "在Supabase控制台创建项目" - "执行npm install @supabase/supabase-js安装客户端" - "使用URL和匿名密钥初始化客户端" - "使用身份验证、数据库、存储和实时功能" token_estimate: entry: 80-95 full: 5000-6000

Supabase Fundamentals

Supabase基础概念

What is Supabase?

什么是Supabase?

Open-source Firebase alternative built on:
  • Postgres Database: Full SQL database with PostgREST API
  • Authentication: Built-in auth with multiple providers
  • Storage: File storage with image transformations
  • Realtime: WebSocket subscriptions to database changes
  • Edge Functions: Serverless functions on Deno runtime
  • Row Level Security: Postgres RLS for data access control
基于以下技术构建的开源Firebase替代方案:
  • Postgres Database: 完整的SQL数据库,搭配PostgREST API
  • Authentication: 内置多提供商身份验证功能
  • Storage: 支持图片转换的文件存储服务
  • Realtime: 数据库变更的WebSocket订阅服务
  • Edge Functions: 运行在Deno runtime上的无服务器函数
  • Row Level Security: 基于Postgres的行级数据访问控制

Project Setup

项目搭建

bash
undefined
bash
undefined

Install Supabase client

安装Supabase客户端

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

Install CLI for local development

安装本地开发CLI

npm install -D supabase
npm install -D supabase

TypeScript types

安装TypeScript类型定义

npm install -D @supabase/supabase-js
undefined
npm install -D @supabase/supabase-js
undefined

Client Initialization

客户端初始化

typescript
// lib/supabase.ts
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!

export const supabase = createClient(supabaseUrl, supabaseAnonKey)

// With TypeScript types
import { Database } from '@/types/supabase'

export const supabase = createClient<Database>(
  supabaseUrl,
  supabaseAnonKey
)
typescript
// lib/supabase.ts
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!

export const supabase = createClient(supabaseUrl, supabaseAnonKey)

// 搭配TypeScript类型使用
import { Database } from '@/types/supabase'

export const supabase = createClient<Database>(
  supabaseUrl,
  supabaseAnonKey
)

Database Operations

数据库操作

PostgREST API Basics

PostgREST API基础

Supabase auto-generates REST API from Postgres schema:
typescript
// SELECT * FROM posts
const { data, error } = await supabase
  .from('posts')
  .select('*')

// SELECT with filters
const { data } = await supabase
  .from('posts')
  .select('*')
  .eq('status', 'published')
  .order('created_at', { ascending: false })
  .limit(10)

// SELECT with joins
const { data } = await supabase
  .from('posts')
  .select(`
    *,
    author:profiles(name, avatar),
    comments(count)
  `)

// INSERT
const { data, error } = await supabase
  .from('posts')
  .insert({ title: 'Hello', content: 'World' })
  .select()
  .single()

// UPDATE
const { data } = await supabase
  .from('posts')
  .update({ status: 'published' })
  .eq('id', postId)
  .select()

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

// UPSERT
const { data } = await supabase
  .from('posts')
  .upsert({ id: 1, title: 'Updated' })
  .select()
Supabase会根据Postgres schema自动生成REST API:
typescript
// 查询所有posts数据
const { data, error } = await supabase
  .from('posts')
  .select('*')

// 带筛选条件的查询
const { data } = await supabase
  .from('posts')
  .select('*')
  .eq('status', 'published')
  .order('created_at', { ascending: false })
  .limit(10)

// 关联查询
const { data } = await supabase
  .from('posts')
  .select(`
    *,
    author:profiles(name, avatar),
    comments(count)
  `)

// 插入数据
const { data, error } = await supabase
  .from('posts')
  .insert({ title: 'Hello', content: 'World' })
  .select()
  .single()

// 更新数据
const { data } = await supabase
  .from('posts')
  .update({ status: 'published' })
  .eq('id', postId)
  .select()

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

// 插入或更新数据(UPSERT)
const { data } = await supabase
  .from('posts')
  .upsert({ id: 1, title: 'Updated' })
  .select()

Advanced Queries

高级查询

typescript
// Full-text search
const { data } = await supabase
  .from('posts')
  .select('*')
  .textSearch('title', 'postgresql', {
    type: 'websearch',
    config: 'english'
  })

// Range queries
const { data } = await supabase
  .from('posts')
  .select('*')
  .gte('created_at', '2024-01-01')
  .lte('created_at', '2024-12-31')

// Array contains
const { data } = await supabase
  .from('posts')
  .select('*')
  .contains('tags', ['postgres', 'supabase'])

// JSON operations
const { data } = await supabase
  .from('users')
  .select('*')
  .eq('metadata->theme', 'dark')

// Count without data
const { count } = await supabase
  .from('posts')
  .select('*', { count: 'exact', head: true })

// Pagination
const pageSize = 10
const page = 2
const { data } = await supabase
  .from('posts')
  .select('*')
  .range(page * pageSize, (page + 1) * pageSize - 1)
typescript
// 全文搜索
const { data } = await supabase
  .from('posts')
  .select('*')
  .textSearch('title', 'postgresql', {
    type: 'websearch',
    config: 'english'
  })

// 范围查询
const { data } = await supabase
  .from('posts')
  .select('*')
  .gte('created_at', '2024-01-01')
  .lte('created_at', '2024-12-31')

// 数组包含查询
const { data } = await supabase
  .from('posts')
  .select('*')
  .contains('tags', ['postgres', 'supabase'])

// JSON操作
const { data } = await supabase
  .from('users')
  .select('*')
  .eq('metadata->theme', 'dark')

// 仅统计数据数量,不返回实际数据
const { count } = await supabase
  .from('posts')
  .select('*', { count: 'exact', head: true })

// 分页查询
const pageSize = 10
const page = 2
const { data } = await supabase
  .from('posts')
  .select('*')
  .range(page * pageSize, (page + 1) * pageSize - 1)

Database Functions and RPC

数据库函数与RPC

typescript
// Call Postgres function
const { data, error } = await supabase
  .rpc('get_trending_posts', {
    days: 7,
    min_score: 10
  })

// Example function in SQL
/*
CREATE OR REPLACE FUNCTION get_trending_posts(
  days INTEGER,
  min_score INTEGER
)
RETURNS TABLE (
  id UUID,
  title TEXT,
  score INTEGER
) AS $$
BEGIN
  RETURN QUERY
  SELECT p.id, p.title, COUNT(v.id)::INTEGER as score
  FROM posts p
  LEFT JOIN votes v ON p.id = v.post_id
  WHERE p.created_at > NOW() - INTERVAL '1 day' * days
  GROUP BY p.id
  HAVING COUNT(v.id) >= min_score
  ORDER BY score DESC;
END;
$$ LANGUAGE plpgsql;
*/
typescript
// 调用Postgres函数
const { data, error } = await supabase
  .rpc('get_trending_posts', {
    days: 7,
    min_score: 10
  })

// SQL示例函数
/*
CREATE OR REPLACE FUNCTION get_trending_posts(
  days INTEGER,
  min_score INTEGER
)
RETURNS TABLE (
  id UUID,
  title TEXT,
  score INTEGER
) AS $$
BEGIN
  RETURN QUERY
  SELECT p.id, p.title, COUNT(v.id)::INTEGER as score
  FROM posts p
  LEFT JOIN votes v ON p.id = v.post_id
  WHERE p.created_at > NOW() - INTERVAL '1 day' * days
  GROUP BY p.id
  HAVING COUNT(v.id) >= min_score
  ORDER BY score DESC;
END;
$$ LANGUAGE plpgsql;
*/

Authentication

身份验证

Email/Password Authentication

邮箱/密码验证

typescript
// Sign up
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'secure-password',
  options: {
    data: {
      name: 'John Doe',
      avatar_url: 'https://...'
    }
  }
})

// Sign in
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'secure-password'
})

// Sign out
const { error } = await supabase.auth.signOut()

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

// Get session
const { data: { session } } = await supabase.auth.getSession()
typescript
// 用户注册
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'secure-password',
  options: {
    data: {
      name: 'John Doe',
      avatar_url: 'https://...'
    }
  }
})

// 用户登录
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'secure-password'
})

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

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

// 获取当前会话
const { data: { session } } = await supabase.auth.getSession()

OAuth Providers

OAuth第三方登录

typescript
// Sign in with OAuth
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'github',
  options: {
    redirectTo: 'http://localhost:3000/auth/callback',
    scopes: 'repo user'
  }
})

// Available providers
// github, google, gitlab, bitbucket, azure, discord, facebook,
// linkedin, notion, slack, spotify, twitch, twitter, apple
typescript
// 使用OAuth登录
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'github',
  options: {
    redirectTo: 'http://localhost:3000/auth/callback',
    scopes: 'repo user'
  }
})

// 支持的第三方提供商
// github, google, gitlab, bitbucket, azure, discord, facebook,
// linkedin, notion, slack, spotify, twitch, twitter, apple

Magic Links

魔法链接登录

typescript
// Send magic link
const { data, error } = await supabase.auth.signInWithOtp({
  email: 'user@example.com',
  options: {
    emailRedirectTo: 'http://localhost:3000/auth/callback'
  }
})

// Verify OTP
const { data, error } = await supabase.auth.verifyOtp({
  email: 'user@example.com',
  token: '123456',
  type: 'email'
})
typescript
// 发送魔法链接
const { data, error } = await supabase.auth.signInWithOtp({
  email: 'user@example.com',
  options: {
    emailRedirectTo: 'http://localhost:3000/auth/callback'
  }
})

// 验证OTP验证码
const { data, error } = await supabase.auth.verifyOtp({
  email: 'user@example.com',
  token: '123456',
  type: 'email'
})

Phone Authentication

手机号验证登录

typescript
// Sign in with phone
const { data, error } = await supabase.auth.signInWithOtp({
  phone: '+1234567890'
})

// Verify phone OTP
const { data, error } = await supabase.auth.verifyOtp({
  phone: '+1234567890',
  token: '123456',
  type: 'sms'
})
typescript
// 手机号登录
const { data, error } = await supabase.auth.signInWithOtp({
  phone: '+1234567890'
})

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

Auth State Management

认证状态管理

typescript
// Listen to auth changes
supabase.auth.onAuthStateChange((event, session) => {
  if (event === 'SIGNED_IN') {
    console.log('User signed in:', session?.user)
  }
  if (event === 'SIGNED_OUT') {
    console.log('User signed out')
  }
  if (event === 'TOKEN_REFRESHED') {
    console.log('Token refreshed')
  }
})

// Update user metadata
const { data, error } = await supabase.auth.updateUser({
  data: { theme: 'dark' }
})

// Change password
const { data, error } = await supabase.auth.updateUser({
  password: 'new-password'
})
typescript
// 监听认证状态变化
supabase.auth.onAuthStateChange((event, session) => {
  if (event === 'SIGNED_IN') {
    console.log('用户已登录:', session?.user)
  }
  if (event === 'SIGNED_OUT') {
    console.log('用户已登出')
  }
  if (event === 'TOKEN_REFRESHED') {
    console.log('令牌已刷新')
  }
})

// 更新用户元数据
const { data, error } = await supabase.auth.updateUser({
  data: { theme: 'dark' }
})

// 修改密码
const { data, error } = await supabase.auth.updateUser({
  password: 'new-password'
})

Row Level Security (RLS)

行级安全(RLS)

RLS Fundamentals

RLS基础概念

Postgres Row Level Security controls data access at the database level:
sql
-- Enable RLS on table
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- Policy: Users can read all published posts
CREATE POLICY "Public posts are viewable by everyone"
ON posts FOR SELECT
USING (status = 'published');

-- Policy: Users can only update their own posts
CREATE POLICY "Users can update own posts"
ON posts FOR UPDATE
USING (auth.uid() = author_id);

-- Policy: Authenticated users can insert posts
CREATE POLICY "Authenticated users can create posts"
ON posts FOR INSERT
WITH CHECK (auth.uid() = author_id);

-- Policy: Users can delete their own posts
CREATE POLICY "Users can delete own posts"
ON posts FOR DELETE
USING (auth.uid() = author_id);
Postgres行级安全功能可在数据库层面控制数据访问:
sql
// 为表启用RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

// 策略:所有用户可查看已发布的帖子
CREATE POLICY "Public posts are viewable by everyone"
ON posts FOR SELECT
USING (status = 'published');

// 策略:用户仅可更新自己发布的帖子
CREATE POLICY "Users can update own posts"
ON posts FOR UPDATE
USING (auth.uid() = author_id);

// 策略:已认证用户可发布帖子
CREATE POLICY "Authenticated users can create posts"
ON posts FOR INSERT
WITH CHECK (auth.uid() = author_id);

// 策略:用户仅可删除自己发布的帖子
CREATE POLICY "Users can delete own posts"
ON posts FOR DELETE
USING (auth.uid() = author_id);

Common RLS Patterns

常见RLS模式

sql
-- Public read, authenticated write
CREATE POLICY "Anyone can view posts"
ON posts FOR SELECT
USING (true);

CREATE POLICY "Authenticated users can create posts"
ON posts FOR INSERT
WITH CHECK (auth.uid() IS NOT NULL);

-- Organization-based access
CREATE POLICY "Users can view org data"
ON documents FOR SELECT
USING (
  organization_id IN (
    SELECT organization_id
    FROM memberships
    WHERE user_id = auth.uid()
  )
);

-- Role-based access
CREATE POLICY "Admins can do anything"
ON posts FOR ALL
USING (
  EXISTS (
    SELECT 1 FROM user_roles
    WHERE user_id = auth.uid()
    AND role = 'admin'
  )
);

-- Time-based access
CREATE POLICY "View published or scheduled posts"
ON posts FOR SELECT
USING (
  status = 'published'
  OR (status = 'scheduled' AND publish_at <= NOW())
);
sql
// 公开可读,认证用户可写
CREATE POLICY "Anyone can view posts"
ON posts FOR SELECT
USING (true);

CREATE POLICY "Authenticated users can create posts"
ON posts FOR INSERT
WITH CHECK (auth.uid() IS NOT NULL);

// 基于组织的访问控制
CREATE POLICY "Users can view org data"
ON documents FOR SELECT
USING (
  organization_id IN (
    SELECT organization_id
    FROM memberships
    WHERE user_id = auth.uid()
  )
);

// 基于角色的访问控制
CREATE POLICY "Admins can do anything"
ON posts FOR ALL
USING (
  EXISTS (
    SELECT 1 FROM user_roles
    WHERE user_id = auth.uid()
    AND role = 'admin'
  )
);

// 基于时间的访问控制
CREATE POLICY "View published or scheduled posts"
ON posts FOR SELECT
USING (
  status = 'published'
  OR (status = 'scheduled' AND publish_at <= NOW())
);

RLS Helper Functions

RLS辅助函数

sql
-- Get current user ID
SELECT auth.uid();

-- Get current user JWT
SELECT auth.jwt();

-- Get specific claim
SELECT auth.jwt()->>'email';

-- Custom claims
SELECT auth.jwt()->'app_metadata'->>'role';
sql
// 获取当前用户ID
SELECT auth.uid();

// 获取当前用户JWT令牌
SELECT auth.jwt();

// 获取特定声明
SELECT auth.jwt()->>'email';

// 获取自定义声明
SELECT auth.jwt()->'app_metadata'->>'role';

Storage

存储服务

File Upload

文件上传

typescript
// Upload file
const { data, error } = await supabase.storage
  .from('avatars')
  .upload('public/avatar1.png', file, {
    cacheControl: '3600',
    upsert: false
  })

// Upload with progress
const { data, error } = await supabase.storage
  .from('avatars')
  .upload('public/avatar1.png', file, {
    onUploadProgress: (progress) => {
      console.log(`${progress.loaded}/${progress.total}`)
    }
  })

// Upload from URL
const { data, error } = await supabase.storage
  .from('avatars')
  .uploadToSignedUrl('path', token, file)
typescript
// 上传文件
const { data, error } = await supabase.storage
  .from('avatars')
  .upload('public/avatar1.png', file, {
    cacheControl: '3600',
    upsert: false
  })

// 带进度监听的上传
const { data, error } = await supabase.storage
  .from('avatars')
  .upload('public/avatar1.png', file, {
    onUploadProgress: (progress) => {
      console.log(`${progress.loaded}/${progress.total}`)
    }
  })

// 通过URL上传文件
const { data, error } = await supabase.storage
  .from('avatars')
  .uploadToSignedUrl('path', token, file)

File Operations

文件操作

typescript
// Download file
const { data, error } = await supabase.storage
  .from('avatars')
  .download('public/avatar1.png')

// Get public URL
const { data } = supabase.storage
  .from('avatars')
  .getPublicUrl('public/avatar1.png')

// Create signed URL (temporary access)
const { data, error } = await supabase.storage
  .from('avatars')
  .createSignedUrl('private/document.pdf', 3600) // 1 hour

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

// Delete file
const { data, error } = await supabase.storage
  .from('avatars')
  .remove(['public/avatar1.png'])

// Move file
const { data, error } = await supabase.storage
  .from('avatars')
  .move('public/avatar1.png', 'public/avatar2.png')
typescript
// 下载文件
const { data, error } = await supabase.storage
  .from('avatars')
  .download('public/avatar1.png')

// 获取公开访问URL
const { data } = supabase.storage
  .from('avatars')
  .getPublicUrl('public/avatar1.png')

// 创建临时签名URL(限时访问)
const { data, error } = await supabase.storage
  .from('avatars')
  .createSignedUrl('private/document.pdf', 3600) // 1小时有效期

// 列出文件
const { data, error } = await supabase.storage
  .from('avatars')
  .list('public', {
    limit: 100,
    offset: 0,
    sortBy: { column: 'name', order: 'asc' }
  })

// 删除文件
const { data, error } = await supabase.storage
  .from('avatars')
  .remove(['public/avatar1.png'])

// 移动文件
const { data, error } = await supabase.storage
  .from('avatars')
  .move('public/avatar1.png', 'public/avatar2.png')

Image Transformations

图片转换

typescript
// Transform image
const { data } = supabase.storage
  .from('avatars')
  .getPublicUrl('avatar1.png', {
    transform: {
      width: 200,
      height: 200,
      resize: 'cover',
      quality: 80
    }
  })

// Available transformations
// width, height, resize (cover|contain|fill),
// quality (1-100), format (origin|jpeg|png|webp)
typescript
// 转换图片
const { data } = supabase.storage
  .from('avatars')
  .getPublicUrl('avatar1.png', {
    transform: {
      width: 200,
      height: 200,
      resize: 'cover',
      quality: 80
    }
  })

// 支持的转换参数
// width, height, resize (cover|contain|fill),
// quality (1-100), format (origin|jpeg|png|webp)

Storage RLS

存储服务RLS

sql
-- Enable RLS on storage
CREATE POLICY "Avatar images are publicly accessible"
ON storage.objects FOR SELECT
USING (bucket_id = 'avatars' AND (storage.foldername(name))[1] = 'public');

CREATE POLICY "Users can upload their own avatar"
ON storage.objects FOR INSERT
WITH CHECK (
  bucket_id = 'avatars'
  AND (storage.foldername(name))[1] = auth.uid()::text
);

CREATE POLICY "Users can delete their own avatar"
ON storage.objects FOR DELETE
USING (
  bucket_id = 'avatars'
  AND (storage.foldername(name))[1] = auth.uid()::text
);
sql
// 为存储服务启用RLS
CREATE POLICY "Avatar images are publicly accessible"
ON storage.objects FOR SELECT
USING (bucket_id = 'avatars' AND (storage.foldername(name))[1] = 'public');

CREATE POLICY "Users can upload their own avatar"
ON storage.objects FOR INSERT
WITH CHECK (
  bucket_id = 'avatars'
  AND (storage.foldername(name))[1] = auth.uid()::text
);

CREATE POLICY "Users can delete their own avatar"
ON storage.objects FOR DELETE
USING (
  bucket_id = 'avatars'
  AND (storage.foldername(name))[1] = auth.uid()::text
);

Realtime Subscriptions

实时订阅

Database Changes

数据库变更订阅

typescript
// Subscribe to inserts
const channel = supabase
  .channel('posts-insert')
  .on(
    'postgres_changes',
    {
      event: 'INSERT',
      schema: 'public',
      table: 'posts'
    },
    (payload) => {
      console.log('New post:', payload.new)
    }
  )
  .subscribe()

// Subscribe to updates
const channel = supabase
  .channel('posts-update')
  .on(
    'postgres_changes',
    {
      event: 'UPDATE',
      schema: 'public',
      table: 'posts',
      filter: 'id=eq.1'
    },
    (payload) => {
      console.log('Updated:', payload.new)
      console.log('Previous:', payload.old)
    }
  )
  .subscribe()

// Subscribe to all changes
const channel = supabase
  .channel('posts-all')
  .on(
    'postgres_changes',
    {
      event: '*',
      schema: 'public',
      table: 'posts'
    },
    (payload) => {
      console.log('Change:', payload)
    }
  )
  .subscribe()

// Unsubscribe
supabase.removeChannel(channel)
typescript
// 订阅帖子插入事件
const channel = supabase
  .channel('posts-insert')
  .on(
    'postgres_changes',
    {
      event: 'INSERT',
      schema: 'public',
      table: 'posts'
    },
    (payload) => {
      console.log('新帖子:', payload.new)
    }
  )
  .subscribe()

// 订阅帖子更新事件
const channel = supabase
  .channel('posts-update')
  .on(
    'postgres_changes',
    {
      event: 'UPDATE',
      schema: 'public',
      table: 'posts',
      filter: 'id=eq.1'
    },
    (payload) => {
      console.log('已更新:', payload.new)
      console.log('更新前:', payload.old)
    }
  )
  .subscribe()

// 订阅帖子所有变更事件
const channel = supabase
  .channel('posts-all')
  .on(
    'postgres_changes',
    {
      event: '*',
      schema: 'public',
      table: 'posts'
    },
    (payload) => {
      console.log('变更:', payload)
    }
  )
  .subscribe()

// 取消订阅
supabase.removeChannel(channel)

Presence (Track Online Users)

在线用户追踪(Presence)

typescript
// Track presence
const channel = supabase.channel('room-1')

// Track current user
channel
  .on('presence', { event: 'sync' }, () => {
    const state = channel.presenceState()
    console.log('Online users:', state)
  })
  .on('presence', { event: 'join' }, ({ key, newPresences }) => {
    console.log('User joined:', newPresences)
  })
  .on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
    console.log('User left:', leftPresences)
  })
  .subscribe(async (status) => {
    if (status === 'SUBSCRIBED') {
      await channel.track({
        user_id: userId,
        online_at: new Date().toISOString()
      })
    }
  })

// Untrack
await channel.untrack()
typescript
// 追踪在线用户
const channel = supabase.channel('room-1')

// 追踪当前用户
channel
  .on('presence', { event: 'sync' }, () => {
    const state = channel.presenceState()
    console.log('在线用户:', state)
  })
  .on('presence', { event: 'join' }, ({ key, newPresences }) => {
    console.log('用户加入:', newPresences)
  })
  .on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
    console.log('用户离开:', leftPresences)
  })
  .subscribe(async (status) => {
    if (status === 'SUBSCRIBED') {
      await channel.track({
        user_id: userId,
        online_at: new Date().toISOString()
      })
    }
  })

// 停止追踪当前用户
await channel.untrack()

Broadcast (Send Messages)

消息广播(Broadcast)

typescript
// Broadcast messages
const channel = supabase.channel('chat-room')

channel
  .on('broadcast', { event: 'message' }, (payload) => {
    console.log('Message:', payload)
  })
  .subscribe()

// Send message
await channel.send({
  type: 'broadcast',
  event: 'message',
  payload: { text: 'Hello', user: 'John' }
})
typescript
// 广播消息
const channel = supabase.channel('chat-room')

channel
  .on('broadcast', { event: 'message' }, (payload) => {
    console.log('消息:', payload)
  })
  .subscribe()

// 发送消息
await channel.send({
  type: 'broadcast',
  event: 'message',
  payload: { text: 'Hello', user: 'John' }
})

Edge Functions

Edge函数

Edge Function Basics

Edge函数基础

Serverless functions on Deno runtime:
bash
undefined
运行在Deno runtime上的无服务器函数:
bash
// 创建函数
supabase functions new my-function

// 本地运行函数
supabase functions serve

// 部署函数
supabase functions deploy my-function

Create function

Edge函数示例

supabase functions new my-function
typescript
// supabase/functions/my-function/index.ts
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'

serve(async (req) => {
  try {
    // 获取认证头
    const authHeader = req.headers.get('Authorization')!

    // 创建Supabase客户端
    const supabase = createClient(
      Deno.env.get('SUPABASE_URL') ?? '',
      Deno.env.get('SUPABASE_ANON_KEY') ?? '',
      { global: { headers: { Authorization: authHeader } } }
    )

    // 验证用户身份
    const { data: { user }, error } = await supabase.auth.getUser()
    if (error) throw error

    // 处理请求
    const { data } = await supabase
      .from('posts')
      .select('*')
      .eq('author_id', user.id)

    return new Response(
      JSON.stringify({ data }),
      { headers: { 'Content-Type': 'application/json' } }
    )
  } catch (error) {
    return new Response(
      JSON.stringify({ error: error.message }),
      { status: 400, headers: { 'Content-Type': 'application/json' } }
    )
  }
})

Serve locally

调用Edge函数

supabase functions serve
typescript
// 从客户端调用
const { data, error } = await supabase.functions.invoke('my-function', {
  body: { name: 'John' }
})

// 带认证信息调用
const { data, error } = await supabase.functions.invoke('my-function', {
  headers: {
    Authorization: `Bearer ${session.access_token}`
  },
  body: { name: 'John' }
})

Deploy

Next.js集成

App Router配置

supabase functions deploy my-function
undefined
typescript
// lib/supabase/client.ts(客户端组件)
import { createBrowserClient } from '@supabase/ssr'

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  )
}

// lib/supabase/server.ts(服务端组件)
import { createServerClient, type CookieOptions } from '@supabase/ssr'
import { cookies } from 'next/headers'

export async function createClient() {
  const cookieStore = await cookies()

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return cookieStore.get(name)?.value
        },
        set(name: string, value: string, options: CookieOptions) {
          cookieStore.set({ name, value, ...options })
        },
        remove(name: string, options: CookieOptions) {
          cookieStore.set({ name, value: '', ...options })
        },
      },
    }
  )
}

// lib/supabase/middleware.ts(中间件)
import { createServerClient, type CookieOptions } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'

export async function updateSession(request: NextRequest) {
  let response = NextResponse.next({
    request: {
      headers: request.headers,
    },
  })

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return request.cookies.get(name)?.value
        },
        set(name: string, value: string, options: CookieOptions) {
          request.cookies.set({ name, value, ...options })
          response = NextResponse.next({
            request: { headers: request.headers },
          })
          response.cookies.set({ name, value, ...options })
        },
        remove(name: string, options: CookieOptions) {
          request.cookies.set({ name, value: '', ...options })
          response = NextResponse.next({
            request: { headers: request.headers },
          })
          response.cookies.set({ name, value: '', ...options })
        },
      },
    }
  )

  await supabase.auth.getUser()
  return response
}

Edge Function Example

中间件配置

typescript
// supabase/functions/my-function/index.ts
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'

serve(async (req) => {
  try {
    // Get auth header
    const authHeader = req.headers.get('Authorization')!

    // Create Supabase client
    const supabase = createClient(
      Deno.env.get('SUPABASE_URL') ?? '',
      Deno.env.get('SUPABASE_ANON_KEY') ?? '',
      { global: { headers: { Authorization: authHeader } } }
    )

    // Verify user
    const { data: { user }, error } = await supabase.auth.getUser()
    if (error) throw error

    // Process request
    const { data } = await supabase
      .from('posts')
      .select('*')
      .eq('author_id', user.id)

    return new Response(
      JSON.stringify({ data }),
      { headers: { 'Content-Type': 'application/json' } }
    )
  } catch (error) {
    return new Response(
      JSON.stringify({ error: error.message }),
      { status: 400, headers: { 'Content-Type': 'application/json' } }
    )
  }
})
typescript
// middleware.ts
import { updateSession } from '@/lib/supabase/middleware'

export async function middleware(request: NextRequest) {
  return await updateSession(request)
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
}

Invoke Edge Function

服务端组件示例

typescript
// From client
const { data, error } = await supabase.functions.invoke('my-function', {
  body: { name: 'John' }
})

// With auth
const { data, error } = await supabase.functions.invoke('my-function', {
  headers: {
    Authorization: `Bearer ${session.access_token}`
  },
  body: { name: 'John' }
})
typescript
// app/posts/page.tsx
import { createClient } from '@/lib/supabase/server'

export default async function PostsPage() {
  const supabase = await createClient()

  const { data: posts } = await supabase
    .from('posts')
    .select('*')
    .order('created_at', { ascending: false })

  return (
    <div>
      {posts?.map((post) => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.content}</p>
        </article>
      ))}
    </div>
  )
}

Next.js Integration

客户端组件示例

App Router Setup

typescript
// lib/supabase/client.ts (Client Component)
import { createBrowserClient } from '@supabase/ssr'

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  )
}

// lib/supabase/server.ts (Server Component)
import { createServerClient, type CookieOptions } from '@supabase/ssr'
import { cookies } from 'next/headers'

export async function createClient() {
  const cookieStore = await cookies()

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return cookieStore.get(name)?.value
        },
        set(name: string, value: string, options: CookieOptions) {
          cookieStore.set({ name, value, ...options })
        },
        remove(name: string, options: CookieOptions) {
          cookieStore.set({ name, value: '', ...options })
        },
      },
    }
  )
}

// lib/supabase/middleware.ts (Middleware)
import { createServerClient, type CookieOptions } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'

export async function updateSession(request: NextRequest) {
  let response = NextResponse.next({
    request: {
      headers: request.headers,
    },
  })

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return request.cookies.get(name)?.value
        },
        set(name: string, value: string, options: CookieOptions) {
          request.cookies.set({ name, value, ...options })
          response = NextResponse.next({
            request: { headers: request.headers },
          })
          response.cookies.set({ name, value, ...options })
        },
        remove(name: string, options: CookieOptions) {
          request.cookies.set({ name, value: '', ...options })
          response = NextResponse.next({
            request: { headers: request.headers },
          })
          response.cookies.set({ name, value: '', ...options })
        },
      },
    }
  )

  await supabase.auth.getUser()
  return response
}
typescript
// app/components/new-post.tsx
'use client'

import { useState } from 'react'
import { createClient } from '@/lib/supabase/client'

export function NewPost() {
  const [title, setTitle] = useState('')
  const supabase = createClient()

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()

    const { data: { user } } = await supabase.auth.getUser()
    if (!user) return

    const { error } = await supabase
      .from('posts')
      .insert({ title, author_id: user.id })

    if (!error) {
      setTitle('')
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="帖子标题"
      />
      <button>发布</button>
    </form>
  )
}

Middleware

服务端操作示例

typescript
// middleware.ts
import { updateSession } from '@/lib/supabase/middleware'

export async function middleware(request: NextRequest) {
  return await updateSession(request)
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
}
typescript
// app/actions/posts.ts
'use server'

import { revalidatePath } from 'next/cache'
import { createClient } from '@/lib/supabase/server'

export async function createPost(formData: FormData) {
  const supabase = await createClient()

  const { data: { user } } = await supabase.auth.getUser()
  if (!user) {
    return { error: '未认证' }
  }

  const title = formData.get('title') as string

  const { error } = await supabase
    .from('posts')
    .insert({ title, author_id: user.id })

  if (error) {
    return { error: error.message }
  }

  revalidatePath('/posts')
  return { success: true }
}

Server Component

TypeScript类型生成

从数据库生成类型

typescript
// app/posts/page.tsx
import { createClient } from '@/lib/supabase/server'

export default async function PostsPage() {
  const supabase = await createClient()

  const { data: posts } = await supabase
    .from('posts')
    .select('*')
    .order('created_at', { ascending: false })

  return (
    <div>
      {posts?.map((post) => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.content}</p>
        </article>
      ))}
    </div>
  )
}
bash
undefined

Client Component

安装CLI

typescript
// app/components/new-post.tsx
'use client'

import { useState } from 'react'
import { createClient } from '@/lib/supabase/client'

export function NewPost() {
  const [title, setTitle] = useState('')
  const supabase = createClient()

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()

    const { data: { user } } = await supabase.auth.getUser()
    if (!user) return

    const { error } = await supabase
      .from('posts')
      .insert({ title, author_id: user.id })

    if (!error) {
      setTitle('')
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="Post title"
      />
      <button>Create</button>
    </form>
  )
}
npm install -D supabase

Server Actions

登录

typescript
// app/actions/posts.ts
'use server'

import { revalidatePath } from 'next/cache'
import { createClient } from '@/lib/supabase/server'

export async function createPost(formData: FormData) {
  const supabase = await createClient()

  const { data: { user } } = await supabase.auth.getUser()
  if (!user) {
    return { error: 'Not authenticated' }
  }

  const title = formData.get('title') as string

  const { error } = await supabase
    .from('posts')
    .insert({ title, author_id: user.id })

  if (error) {
    return { error: error.message }
  }

  revalidatePath('/posts')
  return { success: true }
}
npx supabase login

TypeScript Type Generation

关联项目

Generate Types from Database

bash
undefined
npx supabase link --project-ref your-project-ref

Install CLI

生成类型

npm install -D supabase
npx supabase gen types typescript --project-id your-project-ref > types/supabase.ts

Login

或从本地数据库生成

npx supabase login
npx supabase gen types typescript --local > types/supabase.ts
undefined

Link project

使用生成的类型

npx supabase link --project-ref your-project-ref
typescript
// types/supabase.ts(自动生成)
export type Database = {
  public: {
    Tables: {
      posts: {
        Row: {
          id: string
          title: string
          content: string | null
          author_id: string
          created_at: string
        }
        Insert: {
          id?: string
          title: string
          content?: string | null
          author_id: string
          created_at?: string
        }
        Update: {
          id?: string
          title?: string
          content?: string | null
          author_id?: string
          created_at?: string
        }
      }
    }
  }
}

// 使用示例
import { createClient } from '@supabase/supabase-js'
import { Database } from '@/types/supabase'

const supabase = createClient<Database>(url, key)

// 类型安全的查询
const { data } = await supabase
  .from('posts') // TypeScript会识别该表存在
  .select('title, content') // 自动补全列名
  .single()

// data的类型为 { title: string; content: string | null }

Generate types

Supabase CLI与本地开发

本地开发环境搭建

npx supabase gen types typescript --project-id your-project-ref > types/supabase.ts
bash
undefined

Or from local database

初始化Supabase

npx supabase gen types typescript --local > types/supabase.ts
undefined
npx supabase init

Use Generated Types

启动本地Supabase服务(包含Postgres、Auth、Storage等)

typescript
// types/supabase.ts (generated)
export type Database = {
  public: {
    Tables: {
      posts: {
        Row: {
          id: string
          title: string
          content: string | null
          author_id: string
          created_at: string
        }
        Insert: {
          id?: string
          title: string
          content?: string | null
          author_id: string
          created_at?: string
        }
        Update: {
          id?: string
          title?: string
          content?: string | null
          author_id?: string
          created_at?: string
        }
      }
    }
  }
}

// Usage
import { createClient } from '@supabase/supabase-js'
import { Database } from '@/types/supabase'

const supabase = createClient<Database>(url, key)

// Type-safe queries
const { data } = await supabase
  .from('posts') // TypeScript knows this table exists
  .select('title, content') // Autocomplete for columns
  .single()

// data is typed as { title: string; content: string | null }
npx supabase start

Supabase CLI and Local Development

停止服务

Setup Local Development

bash
undefined
npx supabase stop

Initialize Supabase

重置数据库

npx supabase init
npx supabase db reset

Start local Supabase (Postgres, Auth, Storage, etc.)

查看服务状态

npx supabase start
npx supabase status
undefined

Stop

数据库迁移

npx supabase stop
bash
undefined

Reset database

创建迁移文件

npx supabase db reset
npx supabase migration new create_posts_table

Status

编辑迁移文件

supabase/migrations/20240101000000_create_posts_table.sql

应用迁移

npx supabase status
undefined
npx supabase db push

Database Migrations

拉取远程数据库schema

bash
undefined
npx supabase db pull

Create migration

对比本地与远程数据库差异

npx supabase migration new create_posts_table
npx supabase db diff
undefined

Edit migration file

迁移文件示例

supabase/migrations/20240101000000_create_posts_table.sql

Apply migrations

npx supabase db push
sql
-- supabase/migrations/20240101000000_create_posts_table.sql
CREATE TABLE posts (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  title TEXT NOT NULL,
  content TEXT,
  author_id UUID NOT NULL REFERENCES auth.users(id),
  status TEXT NOT NULL DEFAULT 'draft',
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- 启用RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- 配置策略
CREATE POLICY "Anyone can view published posts"
ON posts FOR SELECT
USING (status = 'published');

CREATE POLICY "Users can create their own posts"
ON posts FOR INSERT
WITH CHECK (auth.uid() = author_id);

CREATE POLICY "Users can update their own posts"
ON posts FOR UPDATE
USING (auth.uid() = author_id);

-- 创建索引
CREATE INDEX posts_author_id_idx ON posts(author_id);
CREATE INDEX posts_status_idx ON posts(status);

-- 为updated_at字段创建触发器
CREATE TRIGGER set_updated_at
BEFORE UPDATE ON posts
FOR EACH ROW
EXECUTE FUNCTION moddatetime(updated_at);

Pull remote schema

安全最佳实践

API密钥管理

npx supabase db pull
typescript
// 切勿在客户端暴露service_role密钥
// 客户端仅使用anon密钥
const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! // 公开密钥
)

// 仅在服务端使用service_role密钥
const supabaseAdmin = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!, // 私密密钥,可绕过RLS
  { auth: { persistSession: false } }
)

Diff local vs remote

RLS最佳实践

npx supabase db diff
undefined
sql
// 始终为表启用RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

// 默认拒绝访问(无策略则无法访问)
// 通过策略明确授予访问权限

// 以不同用户身份测试策略
SET request.jwt.claims.sub = 'user-id';
SELECT * FROM posts; // 模拟该用户测试访问权限

// 仅在管理员操作时临时禁用RLS
// 仅在服务端使用service_role密钥,切勿在客户端使用

Migration Example

输入验证

sql
-- supabase/migrations/20240101000000_create_posts_table.sql
CREATE TABLE posts (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  title TEXT NOT NULL,
  content TEXT,
  author_id UUID NOT NULL REFERENCES auth.users(id),
  status TEXT NOT NULL DEFAULT 'draft',
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Enable RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- Policies
CREATE POLICY "Anyone can view published posts"
ON posts FOR SELECT
USING (status = 'published');

CREATE POLICY "Users can create their own posts"
ON posts FOR INSERT
WITH CHECK (auth.uid() = author_id);

CREATE POLICY "Users can update their own posts"
ON posts FOR UPDATE
USING (auth.uid() = author_id);

-- Indexes
CREATE INDEX posts_author_id_idx ON posts(author_id);
CREATE INDEX posts_status_idx ON posts(status);

-- Trigger for updated_at
CREATE TRIGGER set_updated_at
BEFORE UPDATE ON posts
FOR EACH ROW
EXECUTE FUNCTION moddatetime(updated_at);
typescript
// 在客户端和服务端都进行验证
function validatePost(data: unknown) {
  const schema = z.object({
    title: z.string().min(1).max(200),
    content: z.string().max(10000).optional()
  })

  return schema.parse(data)
}

// 在Edge函数中进行服务端验证
serve(async (req) => {
  const body = await req.json()

  try {
    const validated = validatePost(body)
    // 处理验证后的数据
  } catch (error) {
    return new Response(
      JSON.stringify({ error: '输入无效' }),
      { status: 400 }
    )
  }
})

Security Best Practices

环境变量管理

API Key Management

typescript
// NEVER expose service_role key in client
// Use anon key for client-side
const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! // Public
)

// Service role key only on server
const supabaseAdmin = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!, // Secret, bypasses RLS
  { auth: { persistSession: false } }
)
bash
undefined

RLS Best Practices

.env.local

sql
-- Always enable RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- Default deny (no policy = no access)
-- Explicitly grant access with policies

-- Test policies as different users
SET request.jwt.claims.sub = 'user-id';
SELECT * FROM posts; -- Test as this user

-- Disable RLS only for admin operations
-- Use service_role key from server, never client
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ... # 公开密钥 SUPABASE_SERVICE_ROLE_KEY=eyJ... # 私密密钥,仅服务端使用

Input Validation

生产环境:在托管平台配置环境变量

切勿将.env文件提交到git仓库

typescript
// Validate on client and server
function validatePost(data: unknown) {
  const schema = z.object({
    title: z.string().min(1).max(200),
    content: z.string().max(10000).optional()
  })

  return schema.parse(data)
}

// Server-side validation in Edge Function
serve(async (req) => {
  const body = await req.json()

  try {
    const validated = validatePost(body)
    // Process validated data
  } catch (error) {
    return new Response(
      JSON.stringify({ error: 'Invalid input' }),
      { status: 400 }
    )
  }
})
undefined

Environment Variables

生产环境部署

数据库优化

bash
undefined
sql
// 为常用查询添加索引
CREATE INDEX posts_created_at_idx ON posts(created_at DESC);
CREATE INDEX posts_author_status_idx ON posts(author_id, status);

// 优化全文搜索
CREATE INDEX posts_title_search_idx ON posts
USING GIN (to_tsvector('english', title));

// 分析查询性能
EXPLAIN ANALYZE
SELECT * FROM posts WHERE author_id = 'xxx';

// 清理和分析数据库
VACUUM ANALYZE posts;

.env.local

连接池配置

NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ... # Public SUPABASE_SERVICE_ROLE_KEY=eyJ... # Secret, server-only
typescript
// 为无服务器环境使用连接池
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(url, key, {
  db: {
    schema: 'public',
  },
  auth: {
    persistSession: true,
    autoRefreshToken: true,
  },
  global: {
    headers: { 'x-my-custom-header': 'my-value' },
  },
})

// 在Supabase控制台配置连接池
// 设置 > 数据库 > 连接池

Production: Use environment variables in hosting platform

监控

Never commit .env files to git

undefined
typescript
// 启用查询日志
const supabase = createClient(url, key, {
  global: {
    fetch: async (url, options) => {
      console.log('查询:', url)
      return fetch(url, options)
    }
  }
})

// 在Supabase控制台监控
// - 数据库性能
// - API使用情况
// - 存储使用情况
// - 认证活动

Production Deployment

备份策略

Database Optimization

sql
-- Add indexes for common queries
CREATE INDEX posts_created_at_idx ON posts(created_at DESC);
CREATE INDEX posts_author_status_idx ON posts(author_id, status);

-- Optimize full-text search
CREATE INDEX posts_title_search_idx ON posts
USING GIN (to_tsvector('english', title));

-- Analyze query performance
EXPLAIN ANALYZE
SELECT * FROM posts WHERE author_id = 'xxx';

-- Vacuum and analyze
VACUUM ANALYZE posts;
bash
// 自动备份(专业版及以上)
// 每日备份,支持时间点恢复

// 手动备份
pg_dump -h db.xxx.supabase.co -U postgres -d postgres > backup.sql

// 恢复备份
psql -h db.xxx.supabase.co -U postgres -d postgres < backup.sql

Connection Pooling

Supabase vs Firebase

相似点

typescript
// Use connection pooling for serverless
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(url, key, {
  db: {
    schema: 'public',
  },
  auth: {
    persistSession: true,
    autoRefreshToken: true,
  },
  global: {
    headers: { 'x-my-custom-header': 'my-value' },
  },
})

// Configure pool in Supabase dashboard
// Settings > Database > Connection pooling
  • 后端即服务平台
  • 多提供商身份验证
  • 实时数据同步
  • 文件存储
  • 无服务器函数
  • 免费额度充足

Monitoring

核心差异

typescript
// Enable query logging
const supabase = createClient(url, key, {
  global: {
    fetch: async (url, options) => {
      console.log('Query:', url)
      return fetch(url, options)
    }
  }
})

// Monitor in Supabase Dashboard
// - Database performance
// - API usage
// - Storage usage
// - Auth activity
数据库
  • Supabase: PostgreSQL(SQL,完全可控)
  • Firebase: Firestore(NoSQL,查询能力有限)
查询能力
  • Supabase: 完整SQL支持,支持关联查询、聚合操作
  • Firebase: 过滤能力有限,不支持关联查询
安全机制
  • Supabase: 行级安全(Postgres原生功能)
  • Firebase: 安全规则(自定义语法)
开源属性
  • Supabase: 完全开源,可自行部署
  • Firebase: 闭源,仅支持Google托管
定价模式
  • Supabase: 基于计算资源,价格可预测
  • Firebase: 基于使用量,可能出现费用突增
生态系统
  • Supabase: 兼容Postgres生态(扩展、工具)
  • Firebase: 深度集成Google云平台

Backup Strategy

迁移注意事项

bash
undefined
typescript
// Firestore集合查询
const snapshot = await db
  .collection('posts')
  .where('status', '==', 'published')
  .orderBy('createdAt', 'desc')
  .limit(10)
  .get()

// 等效的Supabase查询
const { data } = await supabase
  .from('posts')
  .select('*')
  .eq('status', 'published')
  .order('created_at', { ascending: false })
  .limit(10)

// Supabase更适合复杂查询
const { data } = await supabase
  .from('posts')
  .select(`
    *,
    author:profiles!inner(name),
    comments(count)
  `)
  .gte('created_at', startDate)
  .lte('created_at', endDate)
  .order('created_at', { ascending: false })
// Firebase需要多次查询+客户端关联

Automatic backups (Pro plan+)

高级模式

Daily backups with point-in-time recovery

乐观更新

Manual backup

pg_dump -h db.xxx.supabase.co -U postgres -d postgres > backup.sql
typescript
'use client'

import { useState, useOptimistic } from 'react'
import { createClient } from '@/lib/supabase/client'

export function PostList({ initialPosts }: { initialPosts: Post[] }) {
  const [posts, setPosts] = useState(initialPosts)
  const [optimisticPosts, addOptimisticPost] = useOptimistic(
    posts,
    (state, newPost: Post) => [...state, newPost]
  )

  const supabase = createClient()

  const createPost = async (title: string) => {
    const tempPost = {
      id: crypto.randomUUID(),
      title,
      created_at: new Date().toISOString()
    }

    addOptimisticPost(tempPost)

    const { data } = await supabase
      .from('posts')
      .insert({ title })
      .select()
      .single()

    if (data) {
      setPosts([...posts, data])
    }
  }

  return (
    <div>
      {optimisticPosts.map((post) => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  )
}

Restore

无限滚动

psql -h db.xxx.supabase.co -U postgres -d postgres < backup.sql
undefined
typescript
'use client'

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

const PAGE_SIZE = 20

export function InfinitePostList() {
  const [posts, setPosts] = useState<Post[]>([])
  const [page, setPage] = useState(0)
  const [hasMore, setHasMore] = useState(true)

  const supabase = createClient()

  useEffect(() => {
    const loadMore = async () => {
      const { data } = await supabase
        .from('posts')
        .select('*')
        .range(page * PAGE_SIZE, (page + 1) * PAGE_SIZE - 1)
        .order('created_at', { ascending: false })

      if (data) {
        setPosts([...posts, ...data])
        setHasMore(data.length === PAGE_SIZE)
      }
    }

    loadMore()
  }, [page])

  return (
    <div>
      {posts.map((post) => (
        <div key={post.id}>{post.title}</div>
      ))}
      {hasMore && (
        <button onClick={() => setPage(page + 1)}>
          加载更多
        </button>
      )}
    </div>
  )
}

Supabase vs Firebase

防抖搜索

Similarities

  • Backend-as-a-Service platform
  • Authentication with multiple providers
  • Realtime data synchronization
  • File storage
  • Serverless functions
  • Generous free tier
typescript
'use client'

import { useState, useEffect } from 'react'
import { createClient } from '@/lib/supabase/client'
import { useDebounce } from '@/hooks/use-debounce'

export function SearchPosts() {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState<Post[]>([])
  const debouncedQuery = useDebounce(query, 300)

  const supabase = createClient()

  useEffect(() => {
    if (!debouncedQuery) {
      setResults([])
      return
    }

    const search = async () => {
      const { data } = await supabase
        .from('posts')
        .select('*')
        .textSearch('title', debouncedQuery)
        .limit(10)

      if (data) setResults(data)
    }

    search()
  }, [debouncedQuery])

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="搜索帖子..."
      />
      {results.map((post) => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  )
}

Key Differences

总结

Database
  • Supabase: PostgreSQL (SQL, full control)
  • Firebase: Firestore (NoSQL, limited queries)
Queries
  • Supabase: Full SQL, joins, aggregations
  • Firebase: Limited filtering, no joins
Security
  • Supabase: Row Level Security (Postgres native)
  • Firebase: Security Rules (custom syntax)
Open Source
  • Supabase: Fully open source, self-hostable
  • Firebase: Proprietary, Google-hosted only
Pricing
  • Supabase: Compute-based, predictable
  • Firebase: Usage-based, can spike
Ecosystem
  • Supabase: Postgres ecosystem (extensions, tools)
  • Firebase: Google Cloud Platform integration
Supabase提供完整的后端平台,包含:
  • Postgres数据库,支持REST和GraphQL API
  • 内置身份验证,支持多提供商
  • 行级安全,实现细粒度访问控制
  • 文件存储,支持图片转换
  • 实时订阅,实现数据实时更新
  • Edge函数,提供无服务器计算能力
  • Next.js集成,支持服务端与客户端组件
  • TypeScript支持,自动生成类型定义
  • 本地开发,通过Supabase CLI实现
  • 生产就绪,包含监控与备份功能
当你需要一个功能完整、基于Postgres、内置身份验证与实时能力的后端平台,且需要良好的TypeScript和Next.js集成时,Supabase是理想选择。

Migration Considerations

typescript
// Firestore collection query
const snapshot = await db
  .collection('posts')
  .where('status', '==', 'published')
  .orderBy('createdAt', 'desc')
  .limit(10)
  .get()

// Equivalent Supabase query
const { data } = await supabase
  .from('posts')
  .select('*')
  .eq('status', 'published')
  .order('created_at', { ascending: false })
  .limit(10)

// Complex queries easier in Supabase
const { data } = await supabase
  .from('posts')
  .select(`
    *,
    author:profiles!inner(name),
    comments(count)
  `)
  .gte('created_at', startDate)
  .lte('created_at', endDate)
  .order('created_at', { ascending: false })
// Firebase would require multiple queries + client-side joins

Advanced Patterns

Optimistic Updates

typescript
'use client'

import { useState, useOptimistic } from 'react'
import { createClient } from '@/lib/supabase/client'

export function PostList({ initialPosts }: { initialPosts: Post[] }) {
  const [posts, setPosts] = useState(initialPosts)
  const [optimisticPosts, addOptimisticPost] = useOptimistic(
    posts,
    (state, newPost: Post) => [...state, newPost]
  )

  const supabase = createClient()

  const createPost = async (title: string) => {
    const tempPost = {
      id: crypto.randomUUID(),
      title,
      created_at: new Date().toISOString()
    }

    addOptimisticPost(tempPost)

    const { data } = await supabase
      .from('posts')
      .insert({ title })
      .select()
      .single()

    if (data) {
      setPosts([...posts, data])
    }
  }

  return (
    <div>
      {optimisticPosts.map((post) => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  )
}

Infinite Scroll

typescript
'use client'

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

const PAGE_SIZE = 20

export function InfinitePostList() {
  const [posts, setPosts] = useState<Post[]>([])
  const [page, setPage] = useState(0)
  const [hasMore, setHasMore] = useState(true)

  const supabase = createClient()

  useEffect(() => {
    const loadMore = async () => {
      const { data } = await supabase
        .from('posts')
        .select('*')
        .range(page * PAGE_SIZE, (page + 1) * PAGE_SIZE - 1)
        .order('created_at', { ascending: false })

      if (data) {
        setPosts([...posts, ...data])
        setHasMore(data.length === PAGE_SIZE)
      }
    }

    loadMore()
  }, [page])

  return (
    <div>
      {posts.map((post) => (
        <div key={post.id}>{post.title}</div>
      ))}
      {hasMore && (
        <button onClick={() => setPage(page + 1)}>
          Load More
        </button>
      )}
    </div>
  )
}

Debounced Search

typescript
'use client'

import { useState, useEffect } from 'react'
import { createClient } from '@/lib/supabase/client'
import { useDebounce } from '@/hooks/use-debounce'

export function SearchPosts() {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState<Post[]>([])
  const debouncedQuery = useDebounce(query, 300)

  const supabase = createClient()

  useEffect(() => {
    if (!debouncedQuery) {
      setResults([])
      return
    }

    const search = async () => {
      const { data } = await supabase
        .from('posts')
        .select('*')
        .textSearch('title', debouncedQuery)
        .limit(10)

      if (data) setResults(data)
    }

    search()
  }, [debouncedQuery])

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search posts..."
      />
      {results.map((post) => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  )
}

Summary

Supabase provides a complete backend platform with:
  • Postgres Database with REST and GraphQL APIs
  • Built-in Authentication with multiple providers
  • Row Level Security for granular access control
  • File Storage with image transformations
  • Realtime Subscriptions for live updates
  • Edge Functions for serverless compute
  • Next.js Integration with Server and Client Components
  • TypeScript Support with auto-generated types
  • Local Development with Supabase CLI
  • Production Ready with monitoring and backups
Use Supabase when a full-featured backend with the power of Postgres, built-in auth, and realtime capabilities is needed, all with excellent TypeScript and Next.js integration.