认证、存储、实时推送、数据库API------4个后端模块,传统开发要数周。本系列逐个拆解,首篇聚焦数据库API(Database Data API):建表即得 API,无需手写后端。
小张,AI创业公司的全栈工程师。老板让他做个产品目录页,展示产品与价格、支持上下架。传统做法要搭后端、写接口、配权限,工期少说1-2周;而在华为云RDS Supabase,建两张表、应用 SDK 直连,几分钟增删改查全跑通。
Part 01 --- 为什么需要 Data API
创表即API:从建表到前端获取数据
建表 → 自动生成端点 /rest/v1/products → 前端 SDK 调用 supabase.from('products').select()
建完表,API 即就绪;前端用 Publishable Key 引入 SDK,受 RLS 约束安全直连,一行代码获取数据:
🗣️ 前端一行查询 → ✅ 返回JSON数组
// JavaScript------前端查询
const { data } = await supabase.from('products').select('id, name, category')
返回示例:
// 返回示例
[{"id":1,"name":"云服务器 ECS","category":"计算"},{"id":2,"name":"对象存储 OBS","category":"存储"}]
没有后端接口代码,没有 DAO 层,没有接口联调------建表即得 API。
Data API 能力速览
|--------------|-----------------|
| 能力 | 说明 |
| 自动生成 CRUD 端点 | 增删改查全覆盖,建表即得 |
| 关联查询 | 支持表间关联,一次拉取关联数据 |
| RLS 行级安全保护 | 每行数据按策略控制访问权限 |
| 无需手写后端 | 不写 DAO、不写接口、不联调 |
原理:PostgREST 自动生成端点
Data API 基于 RDS 托管的 PostgreSQL,由 PostgREST 按表结构自动生成 HTTP 接口,覆盖增删改查。新增表同步生成端点,无需额外配置。
前端引入 Supabase JavaScript SDK 即可直连调用,启用 RLS 后,读写均受行级安全策略约束。
Part 02 --- 前置准备
第1步 获取访问地址
应用的访问地址(内网或公网)及安全组、SSL 等网络配置,详见用户指南「登录应用」章节。完成配置后,记下 {Supabase应用访问地址},第3步要用。
第2步 获取 API Key
API Key 的类型与用途详见用户指南「查看 API Key」章节。日常对接只需两个:客户端用 Publishable Key(受 RLS 约束,可入前端),服务端用 Secret Key(绕 RLS,仅限服务端)。
第3步 安装 SDK 并初始化
安装 SDK 依赖(全文以 JavaScript 语言为例):
npm install @supabase/supabase-js
创建 Supabase 客户端实例:
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = "{Supabase应用访问地址}"
const supabaseApiKey = "{Supabase应用API Key}"
export const supabase = createClient(supabaseUrl, supabaseApiKey)
Part 03 --- 让数据就位:建表与权限配置
前面已备齐地址、Key 与 SDK Client;表还没建,下面建表并配置权限------这步一次配好,后面增删改查直连受益。
场景:产品定价管理。两张表------products 记录产品基础信息,product_pricings 记录各产品计费档位与价格,以 product_id 关联。
权限模型:GRANT 与 RLS 两道门
Supabase 的权限模型不是一道门,而是两道门。
第一道是 GRANT(表级授权),好比🚪门禁卡:决定角色能否进这间屋子、对屋里数据做 SELECT/INSERT/UPDATE/DELETE。没有门禁卡,连门都进不来。
第二道是 RLS(Row Level Security,行级安全策略),好比🗄️抽屉锁:进屋后,哪些抽屉能拉开、哪些对你锁着,由策略决定。它过滤的是"行",让你只看到有权看到的数据。
两层缺一不可。
|------------|----------|---------------------------------------|
| 机制 | 作用层级 | 核心作用 |
| GRANT | 表级 | 决定角色能否对表执行SELECT/INSERT/UPDATE/DELETE |
| RLS Policy | 行级 | 决定哪些行可被哪些角色看到或操作 |
以下 SQL 均可直接复制到 SQL Editor 执行,理解"GRANT 管表级、RLS 管行级"两层即可,无需逐行背语法。
第1步 创建产品表与定价表
两张表结构如下,在 Supabase 的 SQL Editor 里执行:
-- SQL------创建产品表与定价表
CREATE TABLE products (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL,
category TEXT,
description TEXT,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE product_pricings (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
product_id BIGINT NOT NULL REFERENCES products(id) ON DELETE CASCADE,
tier TEXT NOT NULL,
price NUMERIC(10,2) NOT NULL,
unit TEXT DEFAULT '元/月',
effective_from DATE DEFAULT current_date,
created_at TIMESTAMPTZ DEFAULT now()
);
关键字段说明:id 主键用 GENERATED BY DEFAULT AS IDENTITY 自增;product_id 是外键,关联 products(id),加 ON DELETE CASCADE 后删除产品时其定价记录一并清除;price 用 NUMERIC(10,2),总10位、小数2位,金额精度足够。
👆 在 Supabase SQL Editor 左侧的 Table Editor 可看到两张表已创建成功
第2步 启用行级安全
建表后默认无行级保护,需显式启用 RLS:
-- SQL------启用行级安全
ALTER TABLE products ENABLE ROW LEVEL SECURITY;
ALTER TABLE product_pricings ENABLE ROW LEVEL SECURITY;
启用后,所有 Data API 访问须由 RLS 策略放行。启用 RLS 但未配置策略时默认拒绝------anon 与 authenticated 都查不到任何行。
第3步 配置RLS策略
按角色配置策略:访客(anon)只读,登录运营人员(authenticated)可增删改查。
-- SQL------配置RLS策略
CREATE POLICY "anon_select_products" ON public.products FOR SELECT TO anon USING (true);
CREATE POLICY "anon_select_product_pricings" ON public.product_pricings FOR SELECT TO anon USING (true);
CREATE POLICY "authenticated_select_products" ON public.products FOR SELECT TO authenticated USING (true);
CREATE POLICY "authenticated_select_product_pricings" ON public.product_pricings FOR SELECT TO authenticated USING (true);
CREATE POLICY "authenticated_insert_products" ON public.products FOR INSERT TO authenticated WITH CHECK (true);
CREATE POLICY "authenticated_insert_product_pricings" ON public.product_pricings FOR INSERT TO authenticated WITH CHECK (true);
CREATE POLICY "authenticated_update_products" ON public.products FOR UPDATE TO authenticated USING (true) WITH CHECK (true);
CREATE POLICY "authenticated_update_product_pricings" ON public.product_pricings FOR UPDATE TO authenticated USING (true) WITH CHECK (true);
CREATE POLICY "authenticated_delete_products" ON public.products FOR DELETE TO authenticated USING (true);
CREATE POLICY "authenticated_delete_product_pricings" ON public.product_pricings FOR DELETE TO authenticated USING (true);
anon 只配 SELECT 且 USING(true),所有行对访客可见------适合产品目录这类公开数据。authenticated 配齐增删改查四类策略。WITH CHECK(true) 约束写入的新行,USING(true) 控制可见的行;需按用户隔离数据时,把 true 换成条件表达式。
第4步 授予表级权限
第一道门 GRANT 不可少:无 GRANT 则角色无表级权限,RLS 无处生效。
-- SQL------授予表级权限
GRANT SELECT ON TABLE public.products TO anon;
GRANT SELECT ON TABLE public.product_pricings TO anon;
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE public.products TO authenticated;
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE public.product_pricings TO authenticated;
GRANT ALL ON ALL TABLES IN SCHEMA public TO service_role;
权限设计如下:
|---------------------|--------------|----------------------|
| 角色 | products | product_pricings |
| anon(访客) | 只读 | 只读 |
| authenticated(登录用户) | 增删改查 | 增删改查 |
| service_role(服务端) | 全部(绕过RLS) | 全部(绕过RLS) |
第5步 刷新schema缓存
建表或改权限后需刷新 PostgREST 表结构缓存:
-- SQL------刷新schema缓存
NOTIFY pgrst, 'reload schema';
执行后立即生效,Data API 即可识别两张表并生成 GET/POST/PATCH/DELETE 端点。
Part 04 --- 增删改查,前端直连 Data API
表和权限就绪,用 SDK 跑通增删改查------全程不写后端接口。
Key 与角色映射
初始化 SDK 用的 Key 与登录态,决定请求的角色与权限边界。映射如下:
|-----------------|------------------|--------------------|-----------------------|---------------|
| 密钥类型 | 旧称 | 未登录时角色 | 用户登录后角色 | 是否可用于前端 |
| Publishable Key | Anon Key | anon(只读) | authenticated(受RLS约束) | 是,配合 RLS 安全使用 |
| Secret Key | Service Role Key | service_role(绕RLS) | service_role(绕RLS) | 否,严禁放入前端代码 |
未登录请求以 anon 身份发起:读操作 anon 即可,写操作须 authenticated(凭证由认证流程签发)。下面写操作默认已登录。
查询(Select)
顾客浏览所有产品,或按分类筛选只看"计算"类商品。
标准写法:解构出 data 与 error,先判错再取用 data:
// JavaScript------标准查询写法(先判 error 再取 data)
const { data, error } = await supabase
.from('products')
.select('*');
if (error) {
console.error(error);
return;
}
条件查询指定字段,只取需要的列:
🗣️ 请求
// JavaScript------按分类筛选并指定字段
const { data, error } = await supabase
.from('products')
.select('id, name, category')
.eq('category', '计算');
if (error) { console.error(error); return; }
✅ 返回
// 返回示例
[
{ "id": 1, "name": "云服务器 ECS", "category": "计算" },
{ "id": 3, "name": "弹性公网IP", "category": "计算" }
]
判错先于用数据:网络、权限、RLS 异常都反映在 error 里,跳过判错取 data 只得 undefined。
新增(Insert)
运营上架新品。传对象插入单行,传数组批量插入。
🗣️ 请求
// JavaScript------插入单行
await supabase
.from('products')
.insert({
name: '云服务器 ECS',
category: '计算',
description: '弹性云服务器'
});
✅ 返回
// 返回示例
{ "status": 201, "statusText": "Created" }
更新(Update)
运营更新 id 为 1 的商品描述。
🗣️ 请求
// JavaScript------更新指定行(必须带 .eq,否则更新全表)
await supabase
.from('products')
.update({ description: '更新后的描述' })
.eq('id', 1);
✅ 返回
// 返回示例
{ "status": 204, "statusText": "No Content" }
⚠️ update() 须搭配过滤条件(如 .eq('id', 1)),否则会更新全表------如同 SQL 的 UPDATE 不带 WHERE。
更新或新增(Upsert)
运营批量导入商品,数据可能已存在------存在则更新,不存在则新增。upsert 默认按主键判断冲突。
非主键列做冲突判据时,须先建唯一约束:
-- SQL------建唯一约束(在 SQL Editor 中执行)
ALTER TABLE products
ADD CONSTRAINT products_name_key UNIQUE (name);
再用 onConflict 指定冲突列:
🗣️ 请求
// JavaScript------基于 name 列冲突的 upsert
const { data } = await supabase
.from('products')
.upsert(
{ name: '云服务器 ECS', category: '计算' },
{ onConflict: 'name' }
)
.select();
✅ 返回
// 返回示例
[
{ "id": 1, "name": "云服务器 ECS", "category": "计算" }
]
用非主键列做冲突判据时,该列必须先有唯一约束,否则 upsert 退化为纯插入。
删除(Delete)
运营下架某个商品。
🗣️ 请求
// JavaScript------删除指定行(必须带 .eq;外键 CASCADE 级联删定价)
await supabase
.from('products')
.delete()
.eq('id', 1);
✅ 返回
// 返回示例
{ "status": 204, "statusText": "No Content" }
⚠️ 因外键设了 ON DELETE CASCADE,删除商品后其定价档位(product_pricings 关联行)会一并级联删除。
Part 05 --- 关联查询:一次请求拿全产品与定价
外键自动关联
前面几节都是单表查询。但产品列表页常需同时展示名称与价格档位,详情页更需"一产品多条定价"一起渲染。
无需手写 JOIN:PostgREST 通过外键自动识别表间关系,在 select() 用"关联表(字段)"嵌套子表字段,一次请求拿到关联数据。
一对多:产品带出定价
查产品并带出其全部定价,子行以数组返回;加 .order 可排序、加 .eq 可过滤父表。
🗣️ 请求
// JavaScript------查产品并嵌套定价,按分类过滤、按名称排序
const { data } = await supabase
.from('products')
.select(`id, name, category, product_pricings(id, tier, price, unit)`)
.eq('category', '计算')
.order('name')
✅ 返回
// 返回示例
[
{
"id": 1,
"name": "云服务器 ECS",
"category": "计算",
"product_pricings": [
{ "id": 1, "tier": "包月", "price": 99.00, "unit": "元/月" }
]
}
]
product_pricings 是数组------一个产品可有多条定价,"多"侧自动入数组。反向从定价查产品,语法对称,products 则返回对象("一"侧)。
内连接(!inner)
只想返回"存在价格低于 100 的定价档位"的产品,用 !inner 强制内连接,过滤条件写在嵌入列上。
🗣️ 请求
// JavaScript------内连接,只返回有低价档位的产品(!inner 过滤父行而非子行)
const { data } = await supabase
.from('products')
.select('id, name, product_pricings!inner(tier, price)')
.lt('product_pricings.price', 100)
✅ 返回
// 返回示例
[
{
"id": 1,
"name": "云服务器 ECS",
"product_pricings": [{ "tier": "包月", "price": 99.00 }]
}
]
去掉 !inner 则为左连接:仍返回全部产品,但每条只展示价格低于 100 的定价档位。!inner 决定"父行是否被过滤",而非"子行是否被过滤"。
Part 06 --- 安全红线与最佳实践
Key 使用红线
-
客户端只用 Publishable Key:受 RLS 策略约束,可安全写入前端源码。
-
服务端才用 Secret Key:对应 service_role,具 BYPASSRLS 属性、绕过所有行级策略,仅限服务端,严禁暴露到前端。
Key 与角色映射详见 Part 04。
RLS 是数据隔离的核心
两层防护(GRANT 表级 + RLS 行级)详见 Part 03。两种典型坑:
-
只启用 RLS 不 GRANT------角色根本无权访问表,RLS 形同虚设。
-
只 GRANT 不设 RLS 策略------所有行对授权角色可见,等于没有行级保护。
写操作必须走登录态
读可用匿名,写必须先登录。
更新/删除必带过滤条件
写更新或删除时必须搭配过滤条件(eq、match 等)。不带过滤的 update() 改全表,delete() 清空全表------事故级。
错误处理范式
API 返回包含 data 和 error 的对象。先解构、先判错、再用数据:
// JavaScript------错误处理范式
const { data, error } = await supabase.from('products').select('*')
if (error) {
console.error(error)
return
}
先用 data,直接渲染会掩盖异常。
免费体验
建一张表即得一套 API,前端直连,无需为增删改查写后端接口------这就是 RDS Supabase 的 Database Data API。
免费公测Region:华北-北京四、华东-上海一、华南-广州-友好用户环境、华南-广州、西南-贵阳一、中东-利雅得、中国-香港 已开放局点。其他局点暂处邀测阶段,可提交申请。