REST API
Supabase 基于 PostgREST 自动为每个数据表生成 RESTful API。本章节介绍如何使用这些 API 进行数据的增删改查操作。
API 概述
PostgREST 是 Supabase 的核心组件之一,它将 PostgreSQL 数据库直接转换为 RESTful API。这意味着你不需要编写任何后端代码,就可以通过 HTTP 请求操作数据库。
API 地址格式
每个 Supabase 项目的 API 地址格式如下:
https://[project-ref].supabase.co/rest/v1/[table-name]
例如,查询 todos 表:
GET https://abcdefghijklmnop.supabase.co/rest/v1/todos
认证方式
API 请求需要在请求头中包含 API 密钥:
apikey: your-anon-key
Authorization: Bearer your-anon-key
anon 密钥受 RLS 策略保护,可以安全地在客户端使用。service_role 密钥拥有完全权限,只能在服务端使用。
使用 JavaScript SDK
虽然可以直接使用 HTTP 请求,但使用 Supabase JavaScript SDK 更加方便。
查询数据
基本查询:
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'https://your-project.supabase.co',
'your-anon-key'
)
// 查询所有数据
const { data, error } = await supabase
.from('posts')
.select('*')
if (error) {
console.error('查询失败:', error)
} else {
console.log('查询结果:', data)
}
选择特定字段:
// 只查询 title 和 created_at 字段
const { data, error } = await supabase
.from('posts')
.select('id, title, created_at')
条件过滤
使用 filter 方法添加查询条件:
// 等于条件
const { data } = await supabase
.from('posts')
.select('*')
.eq('status', 'published')
// 不等于
const { data } = await supabase
.from('posts')
.select('*')
.neq('status', 'draft')
// 大于、小于
const { data } = await supabase
.from('posts')
.select('*')
.gt('created_at', '2024-01-01') // 大于
.lt('created_at', '2024-12-31') // 小于
.gte('views', 100) // 大于等于
.lte('views', 1000) // 小于等于
// 模糊匹配
const { data } = await supabase
.from('posts')
.select('*')
.like('title', '%Supabase%') // 区分大小写
.ilike('title', '%supabase%') // 不区分大小写
// 包含在列表中
const { data } = await supabase
.from('posts')
.select('*')
.in('status', ['published', 'featured'])
// 空值判断
const { data } = await supabase
.from('posts')
.select('*')
.is('deleted_at', null) // 为空
.not.is('deleted_at', null) // 不为空
排序和分页
// 排序
const { data } = await supabase
.from('posts')
.select('*')
.order('created_at', { ascending: false })
// 多字段排序
const { data } = await supabase
.from('posts')
.select('*')
.order('status', { ascending: true })
.order('created_at', { ascending: false })
// 分页
const { data } = await supabase
.from('posts')
.select('*')
.range(0, 9) // 获取前 10 条(索引 0-9)
// 或使用 limit 和 offset
const { data } = await supabase
.from('posts')
.select('*')
.limit(10)
.offset(20) // 跳过前 20 条
插入数据
插入单条数据:
const { data, error } = await supabase
.from('posts')
.insert({
title: '我的第一篇文章',
content: '这是文章内容...',
status: 'draft',
author_id: 1
})
.select() // 返回插入的数据
if (error) {
console.error('插入失败:', error)
} else {
console.log('插入成功:', data)
}
插入多条数据:
const { data, error } = await supabase
.from('posts')
.insert([
{ title: '文章1', content: '内容1', author_id: 1 },
{ title: '文章2', content: '内容2', author_id: 1 },
{ title: '文章3', content: '内容3', author_id: 2 }
])
.select()
更新数据
更新数据时必须使用 filter 方法指定条件:
// 更新单条记录
const { data, error } = await supabase
.from('posts')
.update({
status: 'published',
published_at: new Date().toISOString()
})
.eq('id', 1)
.select()
// 批量更新
const { data, error } = await supabase
.from('posts')
.update({ status: 'archived' })
.eq('author_id', 1)
.select()
删除数据
// 删除单条记录
const { error } = await supabase
.from('posts')
.delete()
.eq('id', 1)
// 条件删除
const { error } = await supabase
.from('posts')
.delete()
.eq('status', 'draft')
.lt('created_at', '2024-01-01')
关联查询
Supabase 支持通过外键关系进行关联查询。
一对多关系
假设有以下表结构:
-- 用户表
create table users (
id bigint primary key,
name text
);
-- 文章表
create table posts (
id bigint primary key,
title text,
author_id bigint references users(id)
);
查询文章及其作者:
// 查询文章,同时获取作者信息
const { data } = await supabase
.from('posts')
.select(`
id,
title,
author:users (
id,
name
)
`)
// 结果示例
// [
// {
// id: 1,
// title: '文章标题',
// author: { id: 1, name: '张三' }
// }
// ]
反向查询,查询用户及其文章:
const { data } = await supabase
.from('users')
.select(`
id,
name,
posts (
id,
title
)
`)
多对多关系
假设有文章和标签的多对多关系:
create table tags (
id bigint primary key,
name text
);
create table post_tags (
post_id bigint references posts(id),
tag_id bigint references tags(id),
primary key (post_id, tag_id)
);
查询文章及其标签:
const { data } = await supabase
.from('posts')
.select(`
id,
title,
post_tags (
tags (
id,
name
)
)
`)
嵌套过滤
在关联查询中添加过滤条件:
// 只查询已发布的文章
const { data } = await supabase
.from('users')
.select(`
id,
name,
posts (
id,
title
)
`)
.eq('posts.status', 'published')
// 或者使用内联过滤
const { data } = await supabase
.from('users')
.select(`
id,
name,
posts!inner (
id,
title
)
`)
.eq('posts.status', 'published')
使用 HTTP 请求
如果不使用 SDK,也可以直接发送 HTTP 请求。
查询请求
curl 'https://your-project.supabase.co/rest/v1/posts' \
-H "apikey: your-anon-key" \
-H "Authorization: Bearer your-anon-key"
带过滤条件
PostgREST 使用 URL 参数传递过滤条件:
# 等于条件
curl 'https://your-project.supabase.co/rest/v1/posts?status=eq.published' \
-H "apikey: your-anon-key"
# 模糊匹配
curl 'https://your-project.supabase.co/rest/v1/posts?title=ilike.*supabase*' \
-H "apikey: your-anon-key"
# 排序
curl 'https://your-project.supabase.co/rest/v1/posts?order=created_at.desc' \
-H "apikey: your-anon-key"
# 分页
curl 'https://your-project.supabase.co/rest/v1/posts?limit=10&offset=20' \
-H "apikey: your-anon-key"
# 选择字段
curl 'https://your-project.supabase.co/rest/v1/posts?select=id,title' \
-H "apikey: your-anon-key"
插入请求
curl -X POST 'https://your-project.supabase.co/rest/v1/posts' \
-H "apikey: your-anon-key" \
-H "Authorization: Bearer your-anon-key" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{"title":"测试文章","content":"内容"}'
更新请求
curl -X PATCH 'https://your-project.supabase.co/rest/v1/posts?id=eq.1' \
-H "apikey: your-anon-key" \
-H "Authorization: Bearer your-anon-key" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{"status":"published"}'
删除请求
curl -X DELETE 'https://your-project.supabase.co/rest/v1/posts?id=eq.1' \
-H "apikey: your-anon-key" \
-H "Authorization: Bearer your-anon-key"
RPC 函数调用
Supabase 允许调用 PostgreSQL 函数(存储过程)。
创建函数
首先在数据库中创建函数:
create or replace function get_published_posts()
returns setof posts
language sql
as $$
select * from posts where status = 'published' order by created_at desc;
$$;
调用函数
使用 SDK 调用:
const { data, error } = await supabase
.rpc('get_published_posts')
console.log(data)
传递参数:
create or replace function get_posts_by_author(author_id bigint)
returns setof posts
language sql
as $$
select * from posts where author_id = get_posts_by_author.author_id;
$$;
const { data } = await supabase
.rpc('get_posts_by_author', { author_id: 1 })
使用 HTTP 调用:
curl -X POST 'https://your-project.supabase.co/rest/v1/rpc/get_published_posts' \
-H "apikey: your-anon-key" \
-H "Content-Type: application/json"
实时订阅
结合 REST API,可以实现数据的实时订阅:
// 订阅 posts 表的变化
const channel = supabase
.channel('posts-changes')
.on(
'postgres_changes',
{
event: '*', // 监听所有事件
schema: 'public',
table: 'posts'
},
(payload) => {
console.log('数据变化:', payload)
}
)
.subscribe()
// 取消订阅
channel.unsubscribe()
监听特定事件:
// 只监听插入事件
.on(
'postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'posts' },
(payload) => console.log('新文章:', payload.new)
)
// 只监听更新事件
.on(
'postgres_changes',
{ event: 'UPDATE', schema: 'public', table: 'posts' },
(payload) => console.log('更新:', payload.old, '->', payload.new)
)
// 只监听删除事件
.on(
'postgres_changes',
{ event: 'DELETE', schema: 'public', table: 'posts' },
(payload) => console.log('删除:', payload.old)
)
错误处理
Supabase API 返回的错误信息包含详细的原因:
const { data, error } = await supabase
.from('posts')
.insert({ title: '' })
if (error) {
console.error('错误码:', error.code)
console.error('错误信息:', error.message)
console.error('详细信息:', error.details)
console.error('提示:', error.hint)
}
常见错误:
| 错误码 | 说明 | 解决方法 |
|---|---|---|
| 23505 | 唯一约束冲突 | 检查是否有重复数据 |
| 23503 | 外键约束冲突 | 确保关联数据存在 |
| 42501 | 权限不足 | 检查 RLS 策略 |
| PGRST116 | 未找到数据 | 检查查询条件 |
| 22P02 | 数据类型错误 | 检查数据格式 |
性能优化
减少返回字段
只查询需要的字段,减少数据传输:
// 不推荐
.select('*')
// 推荐
.select('id, title, created_at')
批量操作
使用批量插入代替多次单条插入:
// 不推荐
for (const post of posts) {
await supabase.from('posts').insert(post)
}
// 推荐
await supabase.from('posts').insert(posts)
使用索引
确保查询字段有索引:
create index idx_posts_status on posts(status);
create index idx_posts_author_created on posts(author_id, created_at desc);
下一步
掌握 REST API 后,你可以继续学习: