Supabase 速查表
本页面提供 Supabase 常用代码片段的快速参考。
客户端初始化
JavaScript/TypeScript
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'https://your-project.supabase.co',
'your-anon-key'
)
Next.js (客户端)
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
}
Next.js (服务端)
import { createServerClient } 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: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
} catch {}
},
},
}
)
}
数据库操作
查询数据
// 查询所有
const { data, error } = await supabase
.from('posts')
.select('*')
// 选择字段
const { data } = await supabase
.from('posts')
.select('id, title, created_at')
// 条件过滤
const { data } = await supabase
.from('posts')
.select('*')
.eq('status', 'published')
.gt('views', 100)
// 排序
const { data } = await supabase
.from('posts')
.select('*')
.order('created_at', { ascending: false })
// 分页
const { data } = await supabase
.from('posts')
.select('*')
.range(0, 9) // 前 10 条
// 关联查询
const { data } = await supabase
.from('posts')
.select(`
id,
title,
author:users (id, name)
`)
插入数据
// 插入单条
const { data, error } = await supabase
.from('posts')
.insert({ title: '标题', content: '内容' })
.select()
// 插入多条
const { data } = await supabase
.from('posts')
.insert([
{ title: '文章1' },
{ title: '文章2' }
])
.select()
更新数据
const { data, error } = await supabase
.from('posts')
.update({ status: 'published' })
.eq('id', 1)
.select()
删除数据
const { error } = await supabase
.from('posts')
.delete()
.eq('id', 1)
调用 RPC 函数
const { data, error } = await supabase
.rpc('function_name', { param1: 'value1' })
认证操作
邮箱密码
// 注册
const { data, error } = await supabase.auth.signUp({
email: '[email protected]',
password: 'password',
options: {
data: { name: '张三' }
}
})
// 登录
const { data, error } = await supabase.auth.signInWithPassword({
email: '[email protected]',
password: 'password'
})
// 登出
const { error } = await supabase.auth.signOut()
OAuth 登录
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: 'https://your-app.com/auth/callback'
}
})
魔法链接
const { error } = await supabase.auth.signInWithOtp({
email: '[email protected]'
})
获取用户信息
// 获取当前用户
const { data: { user } } = await supabase.auth.getUser()
// 获取会话
const { data: { session } } = await supabase.auth.getSession()
// 监听认证状态
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(event, session) => {
console.log(event, session)
}
)
更新用户
// 更新元数据
const { error } = await supabase.auth.updateUser({
data: { name: '新名字' }
})
// 更新密码
const { error } = await supabase.auth.updateUser({
password: 'new-password'
})
存储操作
上传文件
const { data, error } = await supabase.storage
.from('bucket-name')
.upload('path/file.jpg', file, {
upsert: true
})
获取公开 URL
const { data } = supabase.storage
.from('bucket-name')
.getPublicUrl('path/file.jpg')
console.log(data.publicUrl)
获取签名 URL
const { data, error } = await supabase.storage
.from('bucket-name')
.createSignedUrl('path/file.pdf', 3600) // 1小时有效
下载文件
const { data, error } = await supabase.storage
.from('bucket-name')
.download('path/file.pdf')
列出文件
const { data, error } = await supabase.storage
.from('bucket-name')
.list('folder', {
limit: 100,
sortBy: { column: 'name', order: 'asc' }
})
删除文件
const { data, error } = await supabase.storage
.from('bucket-name')
.remove(['path/file1.jpg', 'path/file2.jpg'])
图片转换
const { data } = supabase.storage
.from('bucket-name')
.getPublicUrl('image.jpg', {
transform: {
width: 200,
height: 200,
resize: 'cover'
}
})
实时订阅
订阅表变更
const channel = supabase
.channel('posts-changes')
.on(
'postgres_changes',
{
event: '*', // INSERT, UPDATE, DELETE, *
schema: 'public',
table: 'posts'
},
(payload) => {
console.log('变更:', payload)
}
)
.subscribe()
// 取消订阅
channel.unsubscribe()
带过滤的订阅
const channel = supabase
.channel('user-posts')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'posts',
filter: 'author_id=eq.1'
},
(payload) => console.log(payload)
)
.subscribe()
广播消息
// 发送
channel.send({
type: 'broadcast',
event: 'cursor-move',
payload: { x: 100, y: 200 }
})
// 接收
channel.on('broadcast', { event: 'cursor-move' }, (payload) => {
console.log(payload)
})
在线状态
const channel = supabase.channel('room-1')
// 监听状态
channel
.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState()
console.log('在线用户:', state)
})
.subscribe()
// 加入
channel.track({
user_id: 'user-1',
name: '张三'
})
// 离开
channel.untrack()
边缘函数
调用函数
const { data, error } = await supabase.functions.invoke('function-name', {
body: { key: 'value' }
})
函数模板
import { createClient } from 'jsr:@supabase/supabase-js@2'
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers':
'authorization, x-client-info, apikey, content-type',
}
Deno.serve(async (req: Request) => {
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_ANON_KEY')!,
{
global: {
headers: { Authorization: req.headers.get('Authorization')! }
}
}
)
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return new Response('Unauthorized', { status: 401 })
}
const body = await req.json()
return new Response(JSON.stringify({ user, body }), {
headers: { 'Content-Type': 'application/json', ...corsHeaders }
})
})
RLS 策略
启用 RLS
alter table posts enable row level security;
常用策略
-- 公开只读
create policy "公开只读" on posts
for select to public
using (status = 'published');
-- 用户访问自己的数据
create policy "用户访问自己数据" on posts
for all to authenticated
using (auth.uid() = author_id)
with check (auth.uid() = author_id);
-- 用户只能读取公开数据和自己数据
create policy "读取策略" on posts
for select to authenticated
using (status = 'published' or auth.uid() = author_id);
向量搜索
创建向量表
create extension if not exists vector;
create table documents (
id bigint primary key generated always as identity,
content text,
embedding vector(1536)
);
create index on documents
using ivfflat (embedding vector_cosine_ops)
with (lists = 100);
向量搜索
-- 余弦相似度搜索
select id, content,
1 - (embedding <=> query_vector) as similarity
from documents
order by embedding <=> query_vector
limit 10;
搜索函数
create or replace function search_documents(
query_embedding vector,
match_threshold float default 0.7,
match_count int default 10
)
returns table (id bigint, content text, similarity float)
language plpgsql as $$
begin
return query
select id, content,
1 - (embedding <=> query_embedding) as similarity
from documents
where 1 - (embedding <=> query_embedding) > match_threshold
order by embedding <=> query_embedding
limit match_count;
end;
$$;
常用 SQL
创建表
create table posts (
id bigint primary key generated always as identity,
title text not null,
content text,
author_id uuid references auth.users(id),
status text default 'draft',
created_at timestamptz default now()
);
创建索引
create index idx_posts_author on posts(author_id);
create index idx_posts_status on posts(status);
create index idx_posts_author_status on posts(author_id, status);
创建触发器
-- 自动更新时间戳
create function update_updated_at()
returns trigger as $$
begin
new.updated_at = now();
return new;
end;
$$ language plpgsql;
create trigger posts_updated_at
before update on posts
for each row
execute function update_updated_at();
创建视图
create view published_posts as
select * from posts where status = 'published';
过滤操作符
| 操作符 | 说明 | 示例 |
|---|---|---|
.eq() | 等于 | .eq('status', 'published') |
.neq() | 不等于 | .neq('status', 'draft') |
.gt() | 大于 | .gt('views', 100) |
.gte() | 大于等于 | .gte('views', 100) |
.lt() | 小于 | .lt('views', 1000) |
.lte() | 小于等于 | .lte('views', 1000) |
.like() | 模糊匹配 | .like('title', '%关键词%') |
.ilike() | 不区分大小写 | .ilike('title', '%keyword%') |
.in() | 包含在列表 | .in('status', ['a', 'b']) |
.is() | 空值判断 | .is('deleted_at', null) |
.contains() | 包含(数组/JSON) | .contains('tags', ['tag1']) |
.range() | 范围 | .range('price', 10, 100) |
错误码
| 错误码 | 说明 |
|---|---|
| 23505 | 唯一约束冲突 |
| 23503 | 外键约束冲突 |
| 42501 | 权限不足 |
| PGRST116 | 未找到数据 |
| 22P02 | 数据类型错误 |
环境变量
# 客户端
NEXT_PUBLIC_SUPABASE_URL=https://project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
# 服务端
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
CLI 命令
# 登录
supabase login
# 初始化项目
supabase init
# 启动本地开发
supabase start
# 创建迁移
supabase migration new migration_name
# 推送迁移
supabase db push
# 部署函数
supabase functions deploy function-name
# 设置密钥
supabase secrets set KEY=value