Supabase 教程
1. 什么是 Supabase?
Supabase 是一个开源的后端即服务 (BaaS) 平台,它基于 PostgreSQL 数据库,提供了实时数据同步、认证、存储、函数等功能。Supabase 的目标是成为 Firebase 的开源替代品,提供类似的功能但基于开源技术。
2. Supabase 的核心概念
2.1 项目 (Project)
项目是 Supabase 资源的组织单元,每个 Supabase 资源都属于一个项目。
2.2 数据库 (Database)
Supabase 使用 PostgreSQL 作为底层数据库,提供了完整的 PostgreSQL 功能。
2.3 实时 (Realtime)
实时功能允许数据在客户端之间实时同步,基于 PostgreSQL 的逻辑复制。
2.4 认证 (Authentication)
认证服务允许用户通过电子邮件/密码、电话号码、OAuth 等方式登录应用。
2.5 存储 (Storage)
存储服务允许用户上传和下载文件,如图片、视频、音频等。
2.6 函数 (Functions)
函数是托管在 Supabase 上的无服务器函数,它可以响应 HTTP 请求或数据库事件。
2.7 边缘函数 (Edge Functions)
边缘函数是部署在全球边缘节点的无服务器函数,提供更低的延迟。
2.8 API
Supabase 自动为数据库生成 RESTful API 和 GraphQL API。
3. Supabase 的安装和配置
3.1 创建 Supabase 项目
- 访问 Supabase 控制台
- 点击 "New Project"
- 输入项目名称、数据库密码并选择区域
- 点击 "Create Project"
3.2 安装 Supabase SDK
使用 npm 或 yarn 安装 Supabase SDK:
# 安装 Supabase 客户端 SDK
npm install @supabase/supabase-js
# 安装 Supabase Admin SDK(用于服务器端)
npm install @supabase/supabase-admin3.3 初始化 Supabase
客户端初始化:
import { createClient } from '@supabase/supabase-js';
// Supabase 配置
const supabaseUrl = 'https://your-project-id.supabase.co';
const supabaseAnonKey = 'your-anon-key';
// 创建 Supabase 客户端
const supabase = createClient(supabaseUrl, supabaseAnonKey);服务器端初始化:
const { createClient } = require('@supabase/supabase-admin');
// Supabase 配置
const supabaseUrl = 'https://your-project-id.supabase.co';
const supabaseServiceKey = 'your-service-key';
// 创建 Supabase Admin 客户端
const supabase = createClient(supabaseUrl, supabaseServiceKey);4. Supabase 的基本使用
4.1 使用认证服务
注册用户:
async function signUp(email, password) {
try {
const { user, error } = await supabase.auth.signUp({
email,
password
});
if (error) {
console.error('Error signing up:', error);
return;
}
console.log('User signed up:', user);
return user;
} catch (error) {
console.error('Error signing up:', error);
throw error;
}
}
// 使用示例
signUp('user@example.com', 'password123');登录用户:
async function signIn(email, password) {
try {
const { user, error } = await supabase.auth.signIn({
email,
password
});
if (error) {
console.error('Error signing in:', error);
return;
}
console.log('User signed in:', user);
return user;
} catch (error) {
console.error('Error signing in:', error);
throw error;
}
}
// 使用示例
signIn('user@example.com', 'password123');登出用户:
async function signOut() {
try {
const { error } = await supabase.auth.signOut();
if (error) {
console.error('Error signing out:', error);
return;
}
console.log('User signed out');
} catch (error) {
console.error('Error signing out:', error);
throw error;
}
}
// 使用示例
signOut();监听认证状态变化:
function listenToAuthChanges(callback) {
return supabase.auth.onAuthStateChange(callback);
}
// 使用示例
const { data: { subscription } } = listenToAuthChanges((event, session) => {
if (session) {
console.log('User is signed in:', session.user);
// 更新 UI 显示已登录状态
} else {
console.log('User is signed out');
// 更新 UI 显示已登出状态
}
});
// 稍后取消订阅
// subscription.unsubscribe();4.2 使用数据库服务
查询数据:
async function getUsers() {
try {
const { data, error } = await supabase
.from('users')
.select('*');
if (error) {
console.error('Error getting users:', error);
return [];
}
console.log('Users:', data);
return data;
} catch (error) {
console.error('Error getting users:', error);
throw error;
}
}
// 使用示例
getUsers();插入数据:
async function createUser(userData) {
try {
const { data, error } = await supabase
.from('users')
.insert(userData);
if (error) {
console.error('Error creating user:', error);
return null;
}
console.log('User created:', data);
return data;
} catch (error) {
console.error('Error creating user:', error);
throw error;
}
}
// 使用示例
createUser({
name: 'John Doe',
email: 'john@example.com',
age: 30
});更新数据:
async function updateUser(id, updates) {
try {
const { data, error } = await supabase
.from('users')
.update(updates)
.eq('id', id);
if (error) {
console.error('Error updating user:', error);
return null;
}
console.log('User updated:', data);
return data;
} catch (error) {
console.error('Error updating user:', error);
throw error;
}
}
// 使用示例
updateUser(1, {
age: 31
});删除数据:
async function deleteUser(id) {
try {
const { data, error } = await supabase
.from('users')
.delete()
.eq('id', id);
if (error) {
console.error('Error deleting user:', error);
return null;
}
console.log('User deleted:', data);
return data;
} catch (error) {
console.error('Error deleting user:', error);
throw error;
}
}
// 使用示例
deleteUser(1);高级查询:
async function getUsersWithFilters() {
try {
const { data, error } = await supabase
.from('users')
.select('id, name, email, age')
.eq('age', '>', 25)
.order('age', { ascending: false })
.limit(10);
if (error) {
console.error('Error getting users:', error);
return [];
}
console.log('Users:', data);
return data;
} catch (error) {
console.error('Error getting users:', error);
throw error;
}
}
// 使用示例
getUsersWithFilters();实时监听:
function listenToUsers(callback) {
return supabase
.from('users')
.on('*', callback)
.subscribe();
}
// 使用示例
const { data: { subscription } } = listenToUsers((payload) => {
console.log('Change received:', payload);
// 更新 UI
});
// 稍后取消订阅
// subscription.unsubscribe();4.3 使用存储服务
上传文件:
async function uploadFile(bucketName, filePath, file) {
try {
const { data, error } = await supabase
.storage
.from(bucketName)
.upload(filePath, file);
if (error) {
console.error('Error uploading file:', error);
return null;
}
console.log('File uploaded:', data);
return data;
} catch (error) {
console.error('Error uploading file:', error);
throw error;
}
}
// 使用示例
const fileInput = document.getElementById('file-input');
uploadFile('avatars', 'user1.jpg', fileInput.files[0]);获取文件 URL:
async function getFileUrl(bucketName, filePath) {
try {
const { data, error } = await supabase
.storage
.from(bucketName)
.getPublicUrl(filePath);
if (error) {
console.error('Error getting file URL:', error);
return null;
}
console.log('File URL:', data.publicUrl);
return data.publicUrl;
} catch (error) {
console.error('Error getting file URL:', error);
throw error;
}
}
// 使用示例
getFileUrl('avatars', 'user1.jpg');删除文件:
async function deleteFile(bucketName, filePath) {
try {
const { data, error } = await supabase
.storage
.from(bucketName)
.remove([filePath]);
if (error) {
console.error('Error deleting file:', error);
return null;
}
console.log('File deleted:', data);
return data;
} catch (error) {
console.error('Error deleting file:', error);
throw error;
}
}
// 使用示例
deleteFile('avatars', 'user1.jpg');4.4 使用函数服务
创建函数:
- 登录 Supabase 控制台
- 导航到 "Functions" 部分
- 点击 "Create a new function"
- 输入函数名称和代码
- 点击 "Deploy"
调用函数:
async function callFunction(functionName, data) {
try {
const { data: result, error } = await supabase
.functions
.invoke(functionName, { body: data });
if (error) {
console.error('Error calling function:', error);
return null;
}
console.log('Function result:', result);
return result;
} catch (error) {
console.error('Error calling function:', error);
throw error;
}
}
// 使用示例
callFunction('hello-world', { name: 'John' });5. Supabase 的高级功能
5.1 使用边缘函数
边缘函数是部署在全球边缘节点的无服务器函数,提供更低的延迟:
- 安装 Supabase CLI:
npm install -g supabase - 初始化边缘函数:
supabase init - 创建边缘函数:
supabase functions new hello-world - 编写边缘函数代码:
// supabase/functions/hello-world/index.ts
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts';
serve(async (req) => {
const { name } = await req.json();
const data = { message: `Hello ${name}!` };
return new Response(JSON.stringify(data), {
headers: { 'Content-Type': 'application/json' },
});
});- 部署边缘函数:
supabase functions deploy hello-world
5.2 使用 PostgreSQL 高级功能
Supabase 基于 PostgreSQL,支持 PostgreSQL 的所有高级功能:
- JSON 支持:存储和查询 JSON 数据
- 全文搜索:使用 PostgreSQL 的全文搜索功能
- 地理空间数据:使用 PostGIS 扩展存储和查询地理空间数据
- 触发器和存储过程:使用 PostgreSQL 的触发器和存储过程
- 视图:创建数据库视图
- 索引:创建各种类型的索引以提高性能
5.3 使用 Row Level Security
Row Level Security (RLS) 允许你定义谁可以访问和修改数据库中的数据:
- 登录 Supabase 控制台
- 导航到 "Database" 部分
- 选择一个表并点击 "Edit"
- 启用 "Row Level Security"
- 创建安全策略
示例:只允许用户访问自己的数据
CREATE POLICY "Users can view own profile" ON users
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can update own profile" ON users
FOR UPDATE USING (auth.uid() = user_id);6. Supabase 的最佳实践
6.1 安全最佳实践
- 使用 Row Level Security:为所有表启用 RLS 并定义适当的安全策略
- 验证用户输入:在客户端和服务器端验证用户输入
- 使用参数化查询:避免 SQL 注入攻击
- 保护服务密钥:不要将服务密钥暴露给客户端
6.2 性能最佳实践
- 使用索引:为常用查询创建适当的索引
- 优化查询:使用适当的查询条件和选择必要的列
- 使用缓存:缓存频繁访问的数据
- 限制实时监听:只监听必要的数据变化
6.3 成本最佳实践
- 监控使用情况:定期检查 Supabase 使用情况和成本
- 优化数据库操作:减少不必要的数据库读取和写入
- 使用适当的存储类型:根据数据访问模式选择合适的存储类型
- 使用边缘函数:对于全球用户,使用边缘函数减少延迟
6.4 代码组织最佳实践
- 模块化:将 Supabase 服务相关代码组织成模块
- 使用异步/await:使用现代 JavaScript 语法处理异步操作
- 编写单元测试:为 Supabase 代码编写单元测试
- 使用 TypeScript:使用 TypeScript 提高代码可靠性
7. 实际应用示例
7.1 构建一个实时聊天应用
// 发送消息
async function sendMessage(roomId, userId, userName, text) {
try {
const { data, error } = await supabase
.from('messages')
.insert({
room_id: roomId,
user_id: userId,
user_name: userName,
text,
created_at: new Date().toISOString()
});
if (error) {
console.error('Error sending message:', error);
return null;
}
return data;
} catch (error) {
console.error('Error sending message:', error);
throw error;
}
}
// 监听消息
function listenToMessages(roomId, callback) {
return supabase
.from(`messages:room_id=eq.${roomId}`)
.on('INSERT', callback)
.subscribe();
}
// 使用示例
const { data: { subscription } } = listenToMessages('room1', (payload) => {
console.log('New message:', payload.new);
// 更新 UI
});
// 发送消息
sendMessage('room1', 'user1', 'John Doe', 'Hello everyone!');7.2 构建一个用户认证系统
// 注册用户
async function register(email, password, userData) {
try {
const { user, error } = await supabase.auth.signUp(
{ email, password },
{ data: userData }
);
if (error) {
console.error('Error registering:', error);
return null;
}
console.log('User registered:', user);
return user;
} catch (error) {
console.error('Error registering:', error);
throw error;
}
}
// 登录用户
async function login(email, password) {
try {
const { user, error } = await supabase.auth.signIn({
email,
password
});
if (error) {
console.error('Error logging in:', error);
return null;
}
console.log('User logged in:', user);
return user;
} catch (error) {
console.error('Error logging in:', error);
throw error;
}
}
// 获取当前用户
async function getCurrentUser() {
try {
const { user, error } = await supabase.auth.user();
if (error) {
console.error('Error getting current user:', error);
return null;
}
return user;
} catch (error) {
console.error('Error getting current user:', error);
throw error;
}
}
// 监听认证状态变化
function listenToAuthChanges(callback) {
return supabase.auth.onAuthStateChange(callback);
}
// 使用示例
const { data: { subscription } } = listenToAuthChanges((event, session) => {
if (session) {
console.log('User is signed in:', session.user);
// 更新 UI 显示已登录状态
} else {
console.log('User is signed out');
// 更新 UI 显示已登出状态
}
});7.3 构建一个文件上传系统
// 上传文件
async function uploadFile(bucketName, filePath, file, onProgress) {
try {
const { data, error } = await supabase
.storage
.from(bucketName)
.upload(filePath, file, {
cacheControl: '3600',
upsert: false
});
if (error) {
console.error('Error uploading file:', error);
return null;
}
// 获取文件 URL
const { data: urlData } = await supabase
.storage
.from(bucketName)
.getPublicUrl(filePath);
console.log('File uploaded:', urlData.publicUrl);
return urlData.publicUrl;
} catch (error) {
console.error('Error uploading file:', error);
throw error;
}
}
// 使用示例
const fileInput = document.getElementById('file-input');
uploadFile('avatars', `user-${Date.now()}.jpg`, fileInput.files[0])
.then((url) => {
console.log('File URL:', url);
// 保存文件 URL 到数据库
});8. Supabase 与其他 BaaS 平台的比较
8.1 Supabase vs Firebase
- 数据模型:Supabase 使用 PostgreSQL(关系型数据库),而 Firebase 使用 Firestore(文档数据库)
- 开源:Supabase 是开源的,而 Firebase 是闭源的
- 定价:Supabase 提供更慷慨的免费额度,而 Firebase 按使用量计费
- 功能:两者都提供类似的功能,包括实时数据、认证、存储、函数等
- 扩展性:Supabase 基于 PostgreSQL,更容易与现有 PostgreSQL 系统集成
8.2 Supabase vs AWS Amplify
- 生态系统:Supabase 是一个完整的 BaaS 平台,而 AWS Amplify 是 AWS 服务的集合
- 灵活性:AWS Amplify 提供更多的 AWS 服务集成,而 Supabase 提供更简单的使用体验
- 定价:两者都按使用量计费,但具体定价模型不同
- 部署:Supabase 部署更简单,而 AWS Amplify 提供更多的部署选项
8.3 Supabase vs Hasura
- 功能:Supabase 提供完整的 BaaS 功能,而 Hasura 主要提供 GraphQL API
- 数据库:两者都基于 PostgreSQL,但 Supabase 提供更多的开箱即用功能
- 认证:Supabase 内置认证,而 Hasura 需要与外部认证服务集成
- 存储:Supabase 内置存储,而 Hasura 需要与外部存储服务集成
9. 总结
Supabase 是一个强大的开源后端即服务 (BaaS) 平台,它基于 PostgreSQL 数据库,提供了实时数据同步、认证、存储、函数等功能。通过本教程的学习,你应该已经掌握了 Supabase 的核心概念、安装配置、基本使用和高级功能。
Supabase 的主要优势包括:
- 开源:基于开源技术,完全透明
- PostgreSQL 功能:提供完整的 PostgreSQL 功能
- 实时数据同步:基于 PostgreSQL 的逻辑复制
- 易于使用:提供了简单易用的 API,减少了开发时间
- 完整的服务套件:提供了一系列服务,包括认证、存储、函数等
- 可扩展性:基于 PostgreSQL,易于与现有系统集成
通过遵循最佳实践,你可以构建安全、高性能、可靠的 Supabase 应用。无论是构建 Web 应用、移动应用还是游戏,Supabase 都能为你提供强大的支持。