Supabase 数据库操作指南
提示
- 以下文档不是 Supabase 数据库操作的完整指南,而是 Nexty.dev 中应用到的一些操作方法,可以帮助你更快地理解模板提供的功能。
- AI 对 Supabase 非常熟悉,你可以大胆使用 AI 来完成各种数据库操作。
Nexty.dev 采用完整的 Supabase 数据库操作架构,提供类型安全的数据库访问、完善的权限控制和统一的操作模式。通过结合 TypeScript 类型系统、Zod 数据验证和 RLS(行级安全)策略,确保数据操作的安全性和可维护性。
基础 CRUD 操作
查询操作(SELECT)
简单查询
const { data: users, error } = await supabase
.from('users')
.select('*') // 或者指定返回字段 .select('email, role')
.eq('role', 'admin');
关联查询
const { data: posts, error } = await supabase
.from('posts')
.select(`
*,
tags (*)
`)
.eq('status', 'published');
分页查询
const { data, error, count } = await supabase
.from('posts')
.select('*', { count: 'exact' }) // { count: 'exact' } -> 带总量统计
.range(0, 9)
.order('created_at', { ascending: false });
插入数据
const { data: newPost, error } = await supabase
.from('posts')
.insert({
title: 'New Post',
slug: 'new-post',
content: 'Post content...',
language: 'en',
status: 'draft',
author_id: user.id
})
.select("id")
.single();
更新数据
const { data: updatedPost, error } = await supabase
.from('posts')
.update({
title: 'Updated Title',
status: 'published'
})
.eq('id', postId)
.select("id")
.single();
删除数据
const { error } = await supabase
.from('posts')
.delete()
.eq('id', postId);
高级查询技巧
条件查询
let query = supabase
.from('posts')
.select('*');
if (filter) {
// 对 title, slug, description 进行模糊查询
const filterValue = `%${filter}%`;
query = query.or(
`title.ilike.${filterValue},slug.ilike.${filterValue},description.ilike.${filterValue}`
);
}
if (status) {
query = query.eq('status', status);
}
const { data, error } = await query
.order('is_pinned', { ascending: false })
.order('created_at', { ascending: false })
.range(from, to);
聚合查询
// 统计 status 字段为 published 的数据的数量
const { count, error } = await supabase
.from('posts')
.select('*', { count: 'exact', head: true })
.eq('status', 'published');
事务处理与 RPC
遇到数据库原子性操作、复杂操作可以利用 Supabase 的 RPC(Remote Procedure Call,远程过程调用)函数来完成。
RPC 是直接在数据库中定义的 PostgreSQL 函数,可以通过 Supabase 客户端直接调用,提供比普通 SQL 查询更强大的功能。
const { error: usageError } = await supabaseAdmin.rpc('upsert_and_set_subscription_credits', {
p_user_id: userId,
p_credits_to_set: creditsToGrant
});
RPC 函数的优势:
- 原子性操作:确保复杂的多步操作要么全部成功,要么全部失败
- 更好的性能:在数据库层面执行,减少网络往返
- 权限控制:通过
SECURITY DEFINER
实现细粒度权限控制 - 复杂业务逻辑:支持条件判断、循环、异常处理等
- 数据一致性:避免竞态条件和并发问题
使用场景:
- 积分扣除系统
- 订单处理流程
- 批量数据更新
- 复杂的计算逻辑
- 需要事务保证的操作
在 Nexty.dev 源码中,你全局搜索 .rpc
可以看到 RPC 函数在用户付费后增加 credits 的方法里应用,它们的函数逻辑在 data
文件夹下对应的数据表定义文件里。
权限控制与安全
管理员权限检查
涉及敏感操作,需要先判断是管理员身份,然后使用 Service Role Key 操作数据库
import { createClient as createAdminClient } from "@supabase/supabase-js";
import { isAdmin } from '@/lib/supabase/isAdmin';
// 判断管理员身份
if (!(await isAdmin())) {
return actionResponse.forbidden("Admin privileges required.");
}
// 管理员使用 Service Role Key 操作数据库
const supabaseAdmin = createAdminClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY! // 仅服务端使用
);
RLS 策略
RLS(Row Level Security,行级安全)是 PostgreSQL 的一个安全特性,允许在数据库表级别控制用户对特定行的访问权限。简单来说,它可以确保用户只能访问属于自己的数据。
在多用户的 SaaS 应用中,不同用户之间的数据必须严格隔离。RLS 提供了数据库级别的安全保障,确保即使客户端代码出现漏洞,也无法访问其他用户的数据。
以下是 RLS 策略的基本用法:
- 启用 RLS
-- 对表启用行级安全
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
-- 强制所有用户都受 RLS 限制(包括表所有者)
ALTER TABLE public.users FORCE ROW LEVEL SECURITY;
- 创建策略
-- 用户只能查看自己的资料
CREATE POLICY "Allow user read their own profile"
ON public.users
FOR SELECT
USING (auth.uid() = id);
-- 用户只能更新自己的资料
CREATE POLICY "Allow user update their own profile"
ON public.users
FOR UPDATE
USING (auth.uid() = id);
在模板提供的 data
文件夹里,所有表设计都能看到创建 RLS 的语句。
提示
对于创建了类似【用户只能更新自己的资料】这样的 RLS 策略的表,管理员是无法直接通过
lib/supabase/server
导出的服务端 client 更新用户资料,只能通过先判断用户是管理员身份,然后使用@supabase/supabase-js
来绕过 RLS 策略修改数据库。
数据库迁移与更新
数据库类型更新
模板使用 Supabase CLI 自动生成和更新 TypeScript 类型定义:
# 生成类型文件
supabase gen types typescript --project-id <your-project-id> --schema public > lib/supabase/types.ts
你需要在每次更新数据库定义后都在本地执行一遍该命令,以确保本地的类型定义是正确的。
命令执行后,lib/supabase/types.ts
文件里的数据库类型定义将更新。
提示:
- Supabase CLI 更多使用方法请查看Supabase 集成
- 如何找到
<your-project-id>
?登录 Supabase 控制台,地址栏/project/
后面的那串字符就是,例如https://supabase.com/dashboard/project/<your-project-id>
。
Schema变更
-- 添加新字段的示例迁移
ALTER TABLE posts ADD COLUMN view_count INTEGER DEFAULT 0;
-- 创建索引
CREATE INDEX IF NOT EXISTS idx_posts_status_language
ON posts(status, language);
-- 更新RLS策略
CREATE POLICY "Users can view published posts" ON posts
FOR SELECT USING (status = 'published');
最佳实践
选择性字段查询
select
尽量只选择必要字段,而不是用 .select('*')
,这样可以提升查询性能
const { data: users, error } = await supabase
.from('users')
.select('email, role')
.eq('role', 'admin');
索引优化查询
通过创建索引优化查询性能
-- 创建复合索引
CREATE INDEX posts_language_status_idx ON posts (language, status);
CREATE INDEX posts_published_at_idx ON posts (published_at DESC);
统一返回格式
模板提供了统一返回格式的方法:
- 对于 Server Actions 可调用
lib/action-response.ts
封装的方法 - 对于 API 可调用
lib/api-response.ts
封装的方法
export type ActionResult<T = any> =
| { success: true; data?: T, customCode?: string }
| { success: false; error: string, customCode?: string };
export const actionResponse = {
success: <T>(data?: T, customCode?: string): ActionResult<T> => {
return { success: true, data, customCode };
},
error: <T>(message: string, customCode?: string): ActionResult<T> => {
return { success: false, error: message, customCode };
},
unauthorized: <T>(message = "Unauthorized", customCode?: string): ActionResult<T> => {
return actionResponse.error(message, customCode);
},
// ...others...
};
模板还提供了不同错误格式的回退处理方法,在 try...catch
代码块里,catch
捕获的错误可以调用 lib/error-utils.ts
的 getErrorMessage
方法进行处理,将避免不规则报错信息导致前端 toast
无法显示真实错误 message 的情况。
Supabase 数据库功能总览
在 Supabase 的 Database 目录下,可以看到所有与数据库定义相关的功能入口,包括:Tables(数据表)、Functions(RPC 函数)、Triggers(触发器)、Indexes(索引)、Policies(策略)。
