跳到主要内容

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 后,你可以继续学习: