Supabase详解
Supabase概述
Supabase是一个开源的Firebase替代品,提供了一套完整的后端服务,包括PostgreSQL数据库、认证、存储、实时功能和边缘函数等。它的核心理念是将传统的PostgreSQL数据库转变为一个实时的API服务,同时保持开源和可扩展性。
Supabase成立于2020年,由Paul Copplestone和Tania Rascia创立,旨在为开发者提供一个开源的无服务器后端解决方案。Supabase的特点是完全开源、与PostgreSQL兼容、提供实时数据同步、简单易用的API,以及强大的认证和存储功能。
Supabase的核心功能
Supabase提供了丰富的功能模块,覆盖了现代应用开发的各个方面:
1. 数据库服务
- PostgreSQL数据库:完全托管的PostgreSQL数据库,提供所有原生PostgreSQL功能
- 自动生成REST API:基于数据库模式自动生成RESTful API
- 自动生成GraphQL API:基于数据库模式自动生成GraphQL API
- 数据导入导出:支持多种格式的数据导入和导出
2. 认证服务
- 用户认证:支持电子邮件/密码、第三方认证(GitHub、Google等)
- JWT令牌:基于JWT的身份验证机制
- 行级安全(RLS)集成:与PostgreSQL的行级安全功能深度集成
- 多因素认证:支持TOTP等多因素认证方式
3. 存储服务
- 文件存储:用于存储和提供用户生成的内容,如照片和视频
- 访问控制:细粒度的访问控制规则
- CDN支持:通过内容分发网络加速文件访问
4. 实时功能
- 实时数据库更改:订阅数据库表的更改并实时接收通知
- 通道和广播:支持自定义实时通道和消息广播
- ** presence系统**:跟踪谁在线以及他们正在做什么
5. 边缘函数
- 无服务器函数:在边缘位置运行的TypeScript函数
- 低延迟:靠近用户的边缘部署,提供低延迟响应
- 集成API:与Supabase的其他服务无缝集成
6. 向量相似度搜索
- pgvector集成:支持向量存储和相似度搜索
- AI应用支持:为AI应用提供向量数据库功能
项目设置与配置
1. 创建Supabase项目
- 访问Supabase控制台
- 点击"New Project"按钮
- 输入项目名称
- 选择数据库密码
- 选择区域
- 点击"Create new project"按钮
2. 安装Supabase客户端库
# 使用npm安装
npm install @supabase/supabase-js
# 或使用yarn安装
yarn add @supabase/supabase-js
3. 初始化Supabase客户端
import { createClient } from '@supabase/supabase-js';
// Supabase配置
const supabaseUrl = 'https://your-project-ref.supabase.co';
const supabaseKey = 'your-anon-key';
// 创建Supabase客户端
const supabase = createClient(supabaseUrl, supabaseKey);
export default supabase;
4. 创建数据库表
可以通过Supabase控制台的Table Editor或SQL Editor创建表:
使用Table Editor:
- 在Supabase控制台中,导航到"Table Editor"部分
- 点击"New Table"按钮
- 定义表名和列
- 设置主键和约束
- 点击"Save"按钮
使用SQL Editor:
- 在Supabase控制台中,导航到"SQL Editor"部分
- 输入SQL创建表语句
- 点击"Run"按钮
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE posts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
title VARCHAR(255) NOT NULL,
content TEXT,
author_id UUID REFERENCES users(id),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
认证服务
1. 用户注册
import supabase from './supabase';
// 用户注册
export const registerUser = async (email, password) => {
try {
const { data, error } = await supabase.auth.signUp({
email,
password
});
if (error) {
throw error;
}
return data;
} catch (error) {
console.error('注册失败:', error.message);
throw error;
}
};
2. 用户登录
import supabase from './supabase';
// 用户登录
export const loginUser = async (email, password) => {
try {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password
});
if (error) {
throw error;
}
return data;
} catch (error) {
console.error('登录失败:', error.message);
throw error;
}
};
3. 第三方登录
import supabase from './supabase';
// GitHub登录
export const signInWithGitHub = async () => {
try {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: 'http://localhost:3000/auth/callback'
}
});
if (error) {
throw error;
}
return data;
} catch (error) {
console.error('GitHub登录失败:', error.message);
throw error;
}
};
// Google登录
export const signInWithGoogle = async () => {
try {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: 'http://localhost:3000/auth/callback'
}
});
if (error) {
throw error;
}
return data;
} catch (error) {
console.error('Google登录失败:', error.message);
throw error;
}
};
4. 用户状态管理
import supabase from './supabase';
// 获取当前用户
export const getCurrentUser = () => {
const { data: { user } } = supabase.auth.getUser();
return user;
};
// 监听用户状态变化
export const onAuthStateChange = (callback) => {
return supabase.auth.onAuthStateChange(callback);
};
// 用户注销
export const logoutUser = async () => {
try {
const { error } = await supabase.auth.signOut();
if (error) {
throw error;
}
} catch (error) {
console.error('注销失败:', error.message);
throw error;
}
};
5. 更新用户资料
import supabase from './supabase';
// 更新用户资料
export const updateUserProfile = async (userData) => {
try {
const { data, error } = await supabase.auth.updateUser({
data: userData
});
if (error) {
throw error;
}
return data;
} catch (error) {
console.error('更新用户资料失败:', error.message);
throw error;
}
};
// 重置密码
export const resetPassword = async (email) => {
try {
const { data, error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: 'http://localhost:3000/reset-password'
});
if (error) {
throw error;
}
return data;
} catch (error) {
console.error('重置密码失败:', error.message);
throw error;
}
};
数据库操作
1. SELECT操作
import supabase from './supabase';
// 获取所有记录
export const getAllRecords = async (tableName, columns = '*') => {
try {
const { data, error } = await supabase
.from(tableName)
.select(columns);
if (error) {
throw error;
}
return data;
} catch (error) {
console.error(`获取${tableName}记录失败:`, error.message);
throw error;
}
};
// 条件查询
export const getRecordsByCondition = async (tableName, conditions, columns = '*') => {
try {
let query = supabase.from(tableName).select(columns);
// 添加条件
if (conditions) {
Object.entries(conditions).forEach(([key, value]) => {
if (typeof value === 'object' && value !== null) {
// 处理操作符条件,如 { 'gt': 10 } 表示 greater than 10
Object.entries(value).forEach(([operator, val]) => {
switch (operator) {
case 'eq':
query = query.eq(key, val);
break;
case 'neq':
query = query.neq(key, val);
break;
case 'gt':
query = query.gt(key, val);
break;
case 'gte':
query = query.gte(key, val);
break;
case 'lt':
query = query.lt(key, val);
break;
case 'lte':
query = query.lte(key, val);
break;
case 'like':
query = query.like(key, val);
break;
case 'ilike':
query = query.ilike(key, val);
break;
case 'in':
query = query.in(key, val);
break;
default:
break;
}
});
} else {
// 默认为等于条件
query = query.eq(key, value);
}
});
}
const { data, error } = await query;
if (error) {
throw error;
}
return data;
} catch (error) {
console.error(`条件查询${tableName}记录失败:`, error.message);
throw error;
}
};
// 分页查询
export const getRecordsWithPagination = async (
tableName,
page = 1,
pageSize = 10,
columns = '*',
sortBy = null,
sortOrder = 'asc'
) => {
try {
let query = supabase.from(tableName).select(columns);
// 添加排序
if (sortBy) {
query = query.order(sortBy, { ascending: sortOrder === 'asc' });
}
// 添加分页
const offset = (page - 1) * pageSize;
query = query.range(offset, offset + pageSize - 1);
const { data, count, error } = await query.count();
if (error) {
throw error;
}
return {
data,
total: count,
page,
pageSize,
totalPages: Math.ceil(count / pageSize)
};
} catch (error) {
console.error(`分页查询${tableName}记录失败:`, error.message);
throw error;
}
};
2. INSERT操作
import supabase from './supabase';
// 插入单条记录
export const insertRecord = async (tableName, data) => {
try {
const { data: insertedData, error } = await supabase
.from(tableName)
.insert(data)
.select(); // 添加select()以返回插入的记录
if (error) {
throw error;
}
return insertedData[0]; // 返回插入的单条记录
} catch (error) {
console.error(`插入${tableName}记录失败:`, error.message);
throw error;
}
};
// 插入多条记录
export const insertMultipleRecords = async (tableName, dataArray) => {
try {
const { data: insertedData, error } = await supabase
.from(tableName)
.insert(dataArray)
.select(); // 添加select()以返回插入的记录
if (error) {
throw error;
}
return insertedData;
} catch (error) {
console.error(`批量插入${tableName}记录失败:`, error.message);
throw error;
}
};
3. UPDATE操作
import supabase from './supabase';
// 更新记录
export const updateRecord = async (tableName, id, data) => {
try {
const { data: updatedData, error } = await supabase
.from(tableName)
.update(data)
.eq('id', id)
.select(); // 添加select()以返回更新的记录
if (error) {
throw error;
}
return updatedData[0]; // 返回更新的单条记录
} catch (error) {
console.error(`更新${tableName}记录失败:`, error.message);
throw error;
}
};
// 条件更新
export const updateRecordsByCondition = async (tableName, conditions, data) => {
try {
let query = supabase.from(tableName).update(data);
// 添加条件
if (conditions) {
Object.entries(conditions).forEach(([key, value]) => {
query = query.eq(key, value);
});
}
const { data: updatedData, error } = await query.select();
if (error) {
throw error;
}
return updatedData;
} catch (error) {
console.error(`条件更新${tableName}记录失败:`, error.message);
throw error;
}
};
4. DELETE操作
import supabase from './supabase';
// 删除单条记录
export const deleteRecord = async (tableName, id) => {
try {
const { data: deletedData, error } = await supabase
.from(tableName)
.delete()
.eq('id', id)
.select(); // 添加select()以返回删除的记录
if (error) {
throw error;
}
return deletedData[0]; // 返回删除的单条记录
} catch (error) {
console.error(`删除${tableName}记录失败:`, error.message);
throw error;
}
};
// 条件删除
export const deleteRecordsByCondition = async (tableName, conditions) => {
try {
let query = supabase.from(tableName).delete();
// 添加条件
if (conditions) {
Object.entries(conditions).forEach(([key, value]) => {
query = query.eq(key, value);
});
}
const { data: deletedData, error } = await query.select();
if (error) {
throw error;
}
return deletedData;
} catch (error) {
console.error(`条件删除${tableName}记录失败:`, error.message);
throw error;
}
};
5. 高级查询
import supabase from './supabase';
// 连接查询(使用PostgreSQL的JOIN)
export const getJoinedRecords = async () => {
try {
const { data, error } = await supabase
.from('posts')
.select(`
id,
title,
content,
created_at,
author_id,
users ( id, email, name )
`);
if (error) {
throw error;
}
return data;
} catch (error) {
console.error('连接查询失败:', error.message);
throw error;
}
};
// 分组和聚合
export const getAggregatedData = async () => {
try {
// 注意:Supabase客户端库不直接支持GROUP BY,但可以使用rpc或raw来执行
const { data, error } = await supabase.rpc('get_posts_by_user');
// 或者使用raw查询
// const { data, error } = await supabase
// .raw('SELECT author_id, COUNT(*) as post_count FROM posts GROUP BY author_id');
if (error) {
throw error;
}
return data;
} catch (error) {
console.error('聚合查询失败:', error.message);
throw error;
}
};
// 使用存储过程(数据库函数)
export const callStoredProcedure = async (functionName, params = {}) => {
try {
const { data, error } = await supabase.rpc(functionName, params);
if (error) {
throw error;
}
return data;
} catch (error) {
console.error(`调用存储过程${functionName}失败:`, error.message);
throw error;
}
};
实时功能
1. 订阅数据库更改
import supabase from './supabase';
// 订阅表的所有更改
export const subscribeToTableChanges = (tableName, callback) => {
const subscription = supabase
.channel('table-changes')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: tableName },
(payload) => {
callback(payload);
}
)
.subscribe();
// 返回取消订阅的函数
return () => {
subscription.unsubscribe();
};
};
// 订阅特定事件的更改
export const subscribeToSpecificEvents = (tableName, events, callback) => {
const subscription = supabase
.channel('specific-events')
.on(
'postgres_changes',
{ event: events, schema: 'public', table: tableName },
(payload) => {
callback(payload);
}
)
.subscribe();
return () => {
subscription.unsubscribe();
};
};
// 订阅带条件的更改
export const subscribeToChangesWithCondition = (tableName, column, value, callback) => {
const subscription = supabase
.channel('conditional-changes')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: tableName,
filter: `${column}=eq.${value}`
},
(payload) => {
callback(payload);
}
)
.subscribe();
return () => {
subscription.unsubscribe();
};
};
2. 自定义通道和广播
import supabase from './supabase';
// 创建自定义通道
export const createCustomChannel = (channelName) => {
const channel = supabase.channel(channelName);
channel.subscribe();
return channel;
};
// 广播消息
export const broadcastMessage = (channel, eventName, message) => {
channel.send({
type: 'broadcast',
event: eventName,
payload: message
});
};
// 接收广播消息
export const listenForBroadcastMessages = (channel, eventName, callback) => {
channel.on('broadcast', { event: eventName }, ({ payload }) => {
callback(payload);
});
};
// 示例:聊天应用中的消息广播
const chatChannel = createCustomChannel('chat-room-1');
// 发送消息
export const sendChatMessage = (message) => {
broadcastMessage(chatChannel, 'new-message', message);
};
// 接收消息
export const receiveChatMessages = (callback) => {
listenForBroadcastMessages(chatChannel, 'new-message', callback);
};
3. Presence功能
import supabase from './supabase';
// 创建带有Presence的通道
export const createPresenceChannel = (channelName, userId, userData) => {
const channel = supabase.channel(channelName, {
config: { presence: { key: userId } }
});
// 追踪用户状态
channel.track(userData);
channel.subscribe();
return channel;
};
// 监听Presence更新
export const listenForPresenceUpdates = (channel, callback) => {
channel.on('presence', { event: 'sync' }, () => {
const presenceState = channel.presenceState();
const users = [];
// 处理presenceState,提取用户信息
Object.values(presenceState).forEach(userSet => {
userSet.forEach(user => {
users.push(user);
});
});
callback(users);
});
channel.on('presence', { event: 'join' }, ({ key, newPresences }) => {
callback(newPresences.map(p => p.payload));
});
channel.on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
callback(leftPresences.map(p => p.payload));
});
};
// 示例:追踪在线用户
const userPresenceChannel = createPresenceChannel(
'online-users',
'user-123',
{ id: 'user-123', name: 'John Doe', avatar: 'https://example.com/avatar.jpg' }
);
// 监听在线用户变化
export const monitorOnlineUsers = (callback) => {
listenForPresenceUpdates(userPresenceChannel, callback);
};
存储服务
1. 上传文件
import supabase from './supabase';
// 上传文件
export const uploadFile = async (file, path = '') => {
try {
const fileName = `${Date.now()}-${file.name}`;
const storagePath = path ? `${path}/${fileName}` : fileName;
const { data, error } = await supabase
.storage
.from('public') // 使用存储桶名称
.upload(storagePath, file, {
cacheControl: '3600',
upsert: false
});
if (error) {
throw error;
}
// 获取文件URL
const { data: { publicUrl } } = supabase
.storage
.from('public')
.getPublicUrl(storagePath);
return {
path: data.path,
url: publicUrl,
fileName
};
} catch (error) {
console.error('文件上传失败:', error.message);
throw error;
}
};
// 上传图片
export const uploadImage = async (file, path = 'images') => {
try {
// 检查文件类型
if (!file.type.match('image.*')) {
throw new Error('只支持图片文件');
}
return await uploadFile(file, path);
} catch (error) {
console.error('图片上传失败:', error.message);
throw error;
}
};
// 批量上传文件
export const uploadMultipleFiles = async (files, path = '') => {
try {
const uploadPromises = Array.from(files).map(file =>
uploadFile(file, path)
);
const results = await Promise.all(uploadPromises);
return results;
} catch (error) {
console.error('批量上传文件失败:', error.message);
throw error;
}
};
2. 下载文件
import supabase from './supabase';
// 获取文件URL
export const getFileUrl = async (filePath, bucket = 'public') => {
try {
const { data: { publicUrl } } = supabase
.storage
.from(bucket)
.getPublicUrl(filePath);
return publicUrl;
} catch (error) {
console.error('获取文件URL失败:', error.message);
throw error;
}
};
// 下载文件内容
export const downloadFile = async (filePath, bucket = 'public') => {
try {
const { data, error } = await supabase
.storage
.from(bucket)
.download(filePath);
if (error) {
throw error;
}
return data;
} catch (error) {
console.error('文件下载失败:', error.message);
throw error;
}
};
// 获取文件元数据
export const getFileMetadata = async (filePath, bucket = 'public') => {
try {
const { data, error } = await supabase
.storage
.from(bucket)
.list(path.dirname(filePath), {
search: path.basename(filePath),
limit: 1
});
if (error) {
throw error;
}
return data.length > 0 ? data[0] : null;
} catch (error) {
console.error('获取文件元数据失败:', error.message);
throw error;
}
};
3. 删除文件
import supabase from './supabase';
// 删除文件
export const deleteFile = async (filePath, bucket = 'public') => {
try {
const { error } = await supabase
.storage
.from(bucket)
.remove([filePath]);
if (error) {
throw error;
}
return true;
} catch (error) {
console.error('文件删除失败:', error.message);
throw error;
}
};
// 批量删除文件
export const deleteMultipleFiles = async (filePaths, bucket = 'public') => {
try {
const { error } = await supabase
.storage
.from(bucket)
.remove(filePaths);
if (error) {
throw error;
}
return true;
} catch (error) {
console.error('批量删除文件失败:', error.message);
throw error;
}
};
边缘函数
1. 设置边缘函数
# 安装Supabase CLI
npm install -g supabase
# 登录Supabase
supabase login
# 初始化项目
supabase init
# 链接到已有的Supabase项目
supabase link --project-ref your-project-ref
# 创建边缘函数
supabase functions new hello-world
2. 编写边缘函数
// supabase/functions/hello-world/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
// 处理请求的函数
const handler = async (req: Request) => {
try {
// 获取请求参数
const { name } = await req.json();
// 创建Supabase客户端
const supabaseClient = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
{
global: { headers: { Authorization: req.headers.get('Authorization')! } },
}
);
// 查询数据库(示例)
const { data, error } = await supabaseClient
.from('users')
.select('id, email')
.limit(1);
if (error) {
throw error;
}
// 返回响应
return new Response(
JSON.stringify({
message: `Hello ${name}!`,
data: data
}),
{
headers: { 'Content-Type': 'application/json' },
status: 200,
}
);
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{
headers: { 'Content-Type': 'application/json' },
status: 400,
}
);
}
};
serve(handler);
3. 部署边缘函数
# 部署边缘函数
supabase functions deploy hello-world
# 部署带环境变量的边缘函数
supabase functions deploy hello-world --no-verify-jwt
4. 在客户端调用边缘函数
import supabase from './supabase';
// 调用边缘函数
export const callEdgeFunction = async (functionName, data = {}, options = {}) => {
try {
const { data: result, error } = await supabase
.functions
.invoke(functionName, {
body: JSON.stringify(data),
...options
});
if (error) {
throw error;
}
return result;
} catch (error) {
console.error(`调用边缘函数${functionName}失败:`, error.message);
throw error;
}
};
// 示例:调用hello-world函数
export const sayHello = async (name) => {
return callEdgeFunction('hello-world', { name });
};
行级安全(RLS)
1. 启用行级安全
可以通过Supabase控制台或SQL启用RLS:
使用控制台:
- 在Supabase控制台中,导航到"Table Editor"部分
- 选择要启用RLS的表
- 点击"Edit Table"按钮
- 在"RLS"选项卡中,启用"Row Level Security"
- 点击"Save Changes"按钮
使用SQL:
-- 启用RLS
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- 启用RLS并强制策略(即使是超级用户也受限制)
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
ALTER TABLE posts FORCE ROW LEVEL SECURITY;
2. 创建安全策略
-- 允许用户只访问和修改自己的数据
CREATE POLICY "Users can view their own data" ON users
FOR SELECT USING (auth.uid() = id);
CREATE POLICY "Users can update their own data" ON users
FOR UPDATE USING (auth.uid() = id);
-- 允许用户只查看和管理自己的帖子
CREATE POLICY "Users can view their own posts" ON posts
FOR SELECT USING (auth.uid() = author_id);
CREATE POLICY "Users can insert 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 POLICY "Users can delete their own posts" ON posts
FOR DELETE USING (auth.uid() = author_id);
-- 允许匿名用户查看公共数据
CREATE POLICY "Public data is visible to everyone" ON public_data
FOR SELECT USING (true);
3. 测试RLS
import supabase from './supabase';
// 测试RLS限制
export const testRLS = async () => {
try {
// 尝试访问所有用户数据(应该只返回当前用户的数据)
const { data: users, error } = await supabase.from('users').select('*');
if (error) {
console.error('RLS测试失败:', error.message);
} else {
console.log('RLS测试成功,返回的数据:', users);
}
} catch (error) {
console.error('RLS测试出错:', error.message);
}
};
性能优化
1. 数据库索引优化
-- 创建单列索引
CREATE INDEX idx_posts_title ON posts USING btree (title);
-- 创建多列索引
CREATE INDEX idx_posts_author_id_created_at ON posts USING btree (author_id, created_at DESC);
-- 创建全文搜索索引
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_posts_content_search ON posts USING gin (content gin_trgm_ops);
-- 查看索引使用情况
EXPLAIN ANALYZE SELECT * FROM posts WHERE title = 'Example Title';
2. 客户端优化
// 只选择需要的列
export const getOptimizedData = async () => {
const { data, error } = await supabase
.from('posts')
.select('id, title, created_at') // 只选择需要的列
.limit(10);
return data;
};
// 使用缓存
export const getCachedData = async (cacheKey, fetchFunction, ttl = 5 * 60 * 1000) => {
// 检查本地存储中的缓存
const cached = localStorage.getItem(cacheKey);
const now = Date.now();
if (cached) {
const { data, timestamp } = JSON.parse(cached);
// 如果缓存未过期,返回缓存的数据
if (now - timestamp < ttl) {
return data;
}
}
// 缓存过期或不存在,重新获取数据
const data = await fetchFunction();
// 存储新的缓存
localStorage.setItem(cacheKey, JSON.stringify({
data,
timestamp: now
}));
return data;
};
// 示例:使用缓存获取帖子列表
const getPostsWithCache = async () => {
return getCachedData('posts_list', async () => {
const { data } = await supabase
.from('posts')
.select('*')
.limit(20);
return data;
});
};
3. 实时连接优化
// 优化实时连接数量
export const optimizeRealtimeConnections = () => {
// 1. 合并相似的订阅
// 2. 只订阅必要的列
// 3. 使用条件过滤减少不必要的更新
// 4. 在不需要时取消订阅
// 示例:只订阅必要的列和带条件的更改
const subscription = supabase
.channel('optimized-channel')
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'posts',
filter: 'category=eq.technology',
select: 'id, title, created_at'
},
(payload) => {
console.log('New technology post:', payload);
}
)
.subscribe();
// 返回取消订阅的函数,方便组件卸载时调用
return () => subscription.unsubscribe();
};
监控与调试
1. 启用日志
在Supabase控制台中,可以启用和查看各种日志:
- 导航到"Database" > "Logs"部分
- 选择要查看的日志类型(PostgreSQL日志、API日志等)
- 设置适当的时间范围和过滤器
- 查看日志内容
2. 使用PostgreSQL的EXPLAIN分析查询
import supabase from './supabase';
// 分析查询性能
export const analyzeQuery = async (query) => {
try {
const { data, error } = await supabase.raw(`EXPLAIN ANALYZE ${query}`);
if (error) {
throw error;
}
return data;
} catch (error) {
console.error('查询分析失败:', error.message);
throw error;
}
};
// 示例:分析帖子查询
const analyzePostsQuery = async () => {
return analyzeQuery('SELECT * FROM posts WHERE author_id = \'user-123\' ORDER BY created_at DESC');
};
3. 使用Supabase的状态端点
// 检查Supabase服务状态
export const checkSupabaseStatus = async () => {
try {
const response = await fetch('https://status.supabase.com/api/v2/status.json');
const status = await response.json();
return status;
} catch (error) {
console.error('检查状态失败:', error.message);
throw error;
}
};
最佳实践
1. 安全最佳实践
- 始终启用行级安全(RLS)保护敏感数据
- 使用参数化查询避免SQL注入攻击
- 为JWT令牌设置适当的过期时间
- 定期轮换数据库密码和API密钥
- 限制数据库连接的IP范围
- 对敏感数据进行加密存储
2. 数据库设计最佳实践
- 遵循数据库规范化原则设计表结构
- 为常用查询创建适当的索引
- 使用外键约束维护数据完整性
- 避免过多的表连接操作
- 合理使用视图简化复杂查询
- 定期运行VACUUM命令优化数据库性能
3. 性能优化最佳实践
- 只查询必要的列和行
- 使用分页限制返回的数据量
- 实现客户端缓存减少重复请求
- 对大型数据集使用异步处理
- 合理使用边缘函数处理低延迟需求
- 优化实时连接,避免不必要的订阅
4. 应用架构最佳实践
- 将业务逻辑与数据访问逻辑分离
- 使用中间件处理认证和授权
- 实现错误处理和日志记录机制
- 使用环境变量管理配置
- 实现数据验证和清理机制
- 考虑使用事务确保数据一致性
总结
Supabase是一个强大的开源后端平台,它将PostgreSQL数据库的强大功能与现代应用开发所需的工具和服务结合在一起。通过使用Supabase,开发者可以快速构建功能丰富的应用,而不必担心基础设施的管理。
Supabase的主要优势包括:完全开源、与PostgreSQL兼容、提供实时数据同步、自动生成API、强大的认证和存储功能等。它特别适合需要快速开发原型或构建中小型应用的开发者。
当然,与任何技术一样,使用Supabase也需要考虑其局限性,如对超大规模应用的支持、复杂查询的性能优化等。但总体而言,Supabase是一个值得考虑的后端解决方案,尤其是对于那些希望避免供应商锁定、喜欢开源技术的开发者来说。