你的团队花了三周时间写 CRUD API——增删改查、分页、过滤、排序、关联查询。每个接口 200 行代码,6 个接口下来 1200 行,外加 Swagger 文档、单元测试、错误处理……而这一切,一个配置文件 + 一行命令就能全部替代。
这就是 PostgREST 做的事:直接读取 PostgreSQL 的 schema 和外键关系,自动生成符合 RESTful 规范的 API,支持过滤、分页、嵌套关联、RPC 调用、批量操作,甚至实时通知。一个不到 10MB 的二进制文件,连接你的数据库就能上线。GitHub 24k+ stars,Supabase 的底层引擎就是它。
📌 记住: PostgREST 不是玩具项目。它在生产环境中处理每秒数万次请求,被 NASA、纽约时报等组织使用。关键在于理解它适合什么场景、不适合什么场景。
🔧 一、PostgREST 核心架构与快速上手
1.1 工作原理:数据库即 API
传统后端架构是 请求 → 路由 → 控制器 → ORM → SQL → 数据库,每多一层就多一份代码和出错的可能。PostgREST 的思路完全不同——它直接查询 PostgreSQL 的 information_schema,解析表结构、外键关系、注释,然后在运行时动态生成 API 端点。
┌──────────┐ HTTP ┌──────────────┐ SQL ┌────────────┐
│ Client │ ────────────→ │ PostgREST │ ───────────→ │ PostgreSQL │
│ (浏览器) │ ←──────────── │ (二进制文件) │ ←──────────── │ (数据库) │
└──────────┘ JSON └──────────────┘ 结果集 └────────────┘
这意味着你的 数据库 schema 就是 API 文档,表结构变了 API 自动变,不需要改代码、不需要重新部署。
1.2 五分钟启动
以下是一个完整的 Docker Compose 配置,启动 PostgreSQL + PostgREST:
# docker-compose.yml — 一键启动 PostgREST + PostgreSQL
version: "3.8"
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: mysecretpassword
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
postgrest:
image: postgrest/postgrest
environment:
PGRST_DB_URI: postgres://authenticator:mysecretpassword@db:5432/myapp
PGRST_DB_SCHEMAS: api
PGRST_DB_ANON_ROLE: web_anon
PGRST_JWT_SECRET: "your-256-bit-secret-key-here"
PGRST_DB_MAX_ROWS: 1000
ports:
- "3001:3000"
depends_on:
- db
volumes:
pgdata:
对应的初始化 SQL:
-- init.sql — 创建 API schema、表结构和安全角色
CREATE SCHEMA api;
-- 创建角色体系
CREATE ROLE web_anon NOLOGIN; -- 匿名用户
CREATE ROLE authenticator NOLOGIN; -- 认证切换角色
CREATE ROLE admin NOLOGIN; -- 管理员角色
GRANT web_anon TO authenticator;
GRANT admin TO authenticator;
-- 创建示例表
CREATE TABLE api.products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
price NUMERIC(10,2) NOT NULL CHECK (price > 0),
category TEXT NOT NULL DEFAULT 'uncategorized',
stock INTEGER NOT NULL DEFAULT 0 CHECK (stock >= 0),
tags TEXT[] DEFAULT '{}',
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE api.orders (
id SERIAL PRIMARY KEY,
product_id INTEGER REFERENCES api.products(id),
quantity INTEGER NOT NULL CHECK (quantity > 0),
user_id UUID NOT NULL,
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'paid', 'shipped', 'completed')),
created_at TIMESTAMPTZ DEFAULT now()
);
-- 授权
GRANT SELECT ON api.products TO web_anon;
GRANT ALL ON api.products TO admin;
GRANT ALL ON api.orders TO admin;
GRANT SELECT, INSERT ON api.orders TO web_anon;
-- 授权序列(INSERT 需要)
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA api TO admin;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA api TO web_anon;
启动后,你就拥有了一个完整的 REST API:
# 查询所有价格低于 100 的电子产品,按价格排序,只返回 name 和 price
curl "http://localhost:3001/products?category=eq.electronics&price=lt.100&order=price&select=name,price"
# 创建订单
curl -X POST http://localhost:3001/orders \
-H "Content-Type: application/json" \
-d '{"product_id": 1, "quantity": 2, "user_id": "550e8400-e29b-41d4-a716-446655440000"}'
# 获取订单及其关联的产品信息(嵌套查询)
curl "http://localhost:3001/orders?select=*,products(name,price)"
💡 提示: PostgREST 自动识别外键关系并生成嵌套查询。不需要写 JOIN,只需要在
select参数中用表名(字段)语法即可。
🚀 二、高级查询与真实场景实战
2.1 复杂过滤与全文搜索
PostgREST 的过滤语法覆盖了 PostgreSQL 的绝大多数操作符:
# 逻辑组合:价格 50-200 且有库存,或标签包含 "sale"
curl -G "http://localhost:3001/products" \
--data-urlencode "or=(and(price.gte.50,price.lte.200,stock.gt.0),tags.cs.{sale})"
# 全文搜索(使用 PostgreSQL 内置的 tsvector)
# 先在数据库中创建计算列
# CREATE FUNCTION api.products_search(api.products)
# RETURNS tsvector AS $$
# SELECT to_tsvector('chinese', $1.name || ' ' || $1.category)
# $$ LANGUAGE SQL IMMUTABLE;
curl "http://localhost:3001/products?products_search=fts.手机"
# JSONB 查询:metadata 中包含 brand=apple 的产品
curl "http://localhost:3001/products?metadata->>brand=eq.apple"
# 数组查询:tags 包含 "new" 或 "hot" 的产品
curl "http://localhost:3001/products?tags=cs.{new}"
curl "http://localhost:3001/products?tags=ov.{new,hot}"
以下是一张过滤操作符速查表:
| 操作符 | 含义 | 示例 | 说明 |
|---|---|---|---|
eq |
等于 | price=eq.100 |
精确匹配 |
gt / gte |
大于 / 大于等于 | price=gt.50 |
数值比较 |
lt / lte |
小于 / 小于等于 | stock=lt.10 |
数值比较 |
like / ilike |
模式匹配 | name=ilike.*手机* |
ilike 不区分大小写 |
in |
IN 查询 | id=in.(1,2,3) |
多值匹配 |
cs |
包含 | tags=cs.{a,b} |
数组/JSON 包含 |
fts |
全文搜索 | search=fts.关键词 |
需 tsvector 列 |
not |
否定 | price=not.eq.0 |
任何操作符可加 not |
or |
逻辑或 | or=(a.eq.1,b.eq.2) |
组合条件 |
2.2 RPC 调用:数据库函数即 API
当简单 CRUD 不够用时,PostgREST 可以将 PostgreSQL 函数暴露为 RPC 端点。这是处理复杂业务逻辑的核心机制:
-- 创建一个带事务的下单函数
CREATE OR REPLACE FUNCTION api.place_order(
p_product_id INTEGER,
p_quantity INTEGER,
p_user_id UUID
) RETURNS JSONB AS $$
DECLARE
v_order api.orders%ROWTYPE;
v_stock INTEGER;
BEGIN
-- 检查库存(加行锁防止超卖)
SELECT stock INTO v_stock
FROM api.products
WHERE id = p_product_id
FOR UPDATE;
IF v_stock IS NULL THEN
RAISE EXCEPTION '产品不存在: %', p_product_id;
END IF;
IF v_stock < p_quantity THEN
RAISE EXCEPTION '库存不足: 需要 %,剩余 %', p_quantity, v_stock;
END IF;
-- 扣减库存
UPDATE api.products
SET stock = stock - p_quantity,
updated_at = now()
WHERE id = p_product_id;
-- 创建订单
INSERT INTO api.orders (product_id, quantity, user_id, status)
VALUES (p_product_id, p_quantity, p_user_id, 'paid')
RETURNING * INTO v_order;
-- 返回结果
RETURN jsonb_build_object(
'order_id', v_order.id,
'status', v_order.status,
'remaining_stock', v_stock - p_quantity
);
END;
$$ LANGUAGE plpgsql;
-- 授权
GRANT EXECUTE ON FUNCTION api.place_order TO web_anon;
调用方式:
# RPC 调用 — 自动映射为 POST /rpc/place_order
curl -X POST http://localhost:3001/rpc/place_order \
-H "Content-Type: application/json" \
-d '{
"p_product_id": 1,
"p_quantity": 2,
"p_user_id": "550e8400-e29b-41d4-a716-446655440000"
}'
# 返回结果
# {"order_id": 42, "status": "paid", "remaining_stock": 8}
⚠️ 警告: RPC 函数中的业务逻辑必须自己保证原子性。PostgREST 会为每个请求开启一个事务,但如果函数内部没有正确的异常处理,部分操作可能已提交。务必使用
BEGIN...EXCEPTION...END或FOR UPDATE行锁。
2.3 分页、计数与批量操作
# 分页:offset + limit(传统分页)
curl "http://localhost:3001/products?limit=20&offset=40"
# 获取总数(返回在 Response-Total-Count 头中)
curl -I "http://localhost:3001/products" \
-H "Prefer: count=exact"
# Response-Total-Count: 156
# 游标分页(推荐大数据量)— 基于 id 做 keyset pagination
curl "http://localhost://3001/products?id=gt.20&order=id&limit=20"
# 批量插入
curl -X POST http://localhost:3001/products \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '[
{"name": "手机A", "price": 2999, "category": "electronics", "stock": 100},
{"name": "手机B", "price": 3999, "category": "electronics", "stock": 50},
{"name": "手机C", "price": 1999, "category": "electronics", "stock": 200}
]'
# 批量更新(通过过滤条件)
curl -X PATCH "http://localhost:3001/products?category=eq.electronics" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{"metadata": "{\"on_sale\": true}"}'
💡 提示:
Prefer: return=representation让 POST/PATCH/DELETE 返回修改后的数据,这在批量操作时非常有用——你可以直接看到操作结果。
🛡️ 三、行级安全与认证体系
3.1 JWT 认证集成
PostgREST 使用 JWT(JSON Web Token)进行身份认证。它从 JWT 的 role claim 中获取数据库角色,实现权限切换:
// Node.js — 生成 PostgREST 兼容的 JWT
import jwt from 'jsonwebtoken';
const SECRET = 'your-256-bit-secret-key-here';
function generateToken(user) {
return jwt.sign(
{
role: user.isAdmin ? 'admin' : 'web_anon',
// 自定义 claim,可在 RLS 策略中使用 current_setting()
user_id: user.id,
email: user.email,
},
SECRET,
{ algorithm: 'HS256', expiresIn: '24h' }
);
}
// 使用示例
const token = generateToken({
id: '550e8400-e29b-41d4-a716-446655440000',
email: 'dev@jsjson.com',
isAdmin: false,
});
console.log(token);
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
在请求中携带 JWT:
# 带 JWT 的请求
curl http://localhost:3001/orders \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
# PostgREST 会自动执行:
# SET ROLE web_anon;
# SET request.jwt.claims -> '{"user_id":"...","email":"..."}';
# 然后执行查询,RLS 策略生效
3.2 行级安全策略(RLS)
RLS 是 PostgREST 安全模型的核心。它让你在数据库层面控制「谁能看什么数据」:
-- 启用 RLS
ALTER TABLE api.orders ENABLE ROW LEVEL SECURITY;
-- 策略 1:普通用户只能看自己的订单
CREATE POLICY user_orders_select ON api.orders
FOR SELECT
TO web_anon
USING (user_id = current_setting('request.jwt.claims', true)::json->>'user_id');
-- 策略 2:普通用户只能创建自己的订单
CREATE POLICY user_orders_insert ON api.orders
FOR INSERT
TO web_anon
WITH CHECK (user_id = current_setting('request.jwt.claims', true)::json->>'user_id');
-- 策略 3:管理员可以看所有订单
CREATE POLICY admin_orders_all ON api.orders
FOR ALL
TO admin
USING (true);
-- 产品表:所有人可读,只有管理员可写
ALTER TABLE api.products ENABLE ROW LEVEL SECURITY;
CREATE POLICY products_read ON api.products
FOR SELECT
TO web_anon
USING (true);
CREATE POLICY products_admin_write ON api.products
FOR ALL
TO admin
USING (true);
⚠️ 警告: 如果你启用了 RLS 但没有创建任何策略,默认行为是拒绝所有访问。这会导致「表存在但查不到数据」的困惑。开发阶段建议先用
USING (true)放开,再逐步收紧。
3.3 安全架构对比
| 方案 | 安全层 | 代码量 | 灵活性 | 学习成本 |
|---|---|---|---|---|
| PostgREST + RLS | 数据库层 | ⭐ 极少 | 中等 | 中(需学 RLS) |
| Express + ORM | 应用层 | ⭐⭐⭐⭐ 多 | 极高 | 低 |
| Hasura | 引擎层 | ⭐⭐ 少 | 高 | 中 |
| Supabase(封装 PostgREST) | 平台层 | ⭐⭐ 少 | 高 | 低 |
⚡ 关键结论: PostgREST 的安全模型把权限逻辑下沉到数据库层,好处是不可能绕过——不管从哪个入口访问数据库,RLS 都会生效。坏处是调试困难,需要用 SET ROLE 模拟不同用户身份来排查。
📊 四、性能调优与生产部署
4.1 连接池与并发
PostgREST 内置了连接池(基于 libpq),默认配置通常够用,但高并发场景需要调优:
# postgrest.conf — 生产环境推荐配置
db-uri = "postgres://authenticator:password@localhost:5432/myapp"
db-schemas = "api"
db-anon-role = "web_anon"
jwt-secret = "your-secret"
# 连接池配置
db-pool = 20 # 连接池大小(默认 10)
db-pool-acquisition-timeout = 10 # 获取连接超时(秒)
db-pool-max-lifetime = 1800 # 连接最大生命周期(秒)
# 性能限制
db-max-rows = 1000 # 单次查询最大返回行数
db-extra-search-path = "public" # 额外搜索路径
# 日志
log-level = "warn" # 日志级别
4.2 PostgreSQL 索引优化
PostgREST 的查询直接变成 SQL,所以数据库索引就是你的 API 性能基础:
-- 为常用过滤字段创建索引
CREATE INDEX idx_products_category ON api.products(category);
CREATE INDEX idx_products_price ON api.products(price);
CREATE INDEX idx_products_created_at ON api.products(created_at DESC);
-- 复合索引:覆盖常见的组合查询
CREATE INDEX idx_products_cat_price ON api.products(category, price);
-- JSONB GIN 索引:支持 metadata 字段的高效查询
CREATE INDEX idx_products_metadata ON api.products USING GIN(metadata);
-- 数组 GIN 索引:支持 tags 字段的包含查询
CREATE INDEX idx_products_tags ON api.products USING GIN(tags);
-- 部分索引:只索引有库存的产品(更小、更快)
CREATE INDEX idx_products_in_stock ON api.products(price)
WHERE stock > 0;
-- 查看慢查询
EXPLAIN ANALYZE
SELECT * FROM api.products
WHERE category = 'electronics' AND price < 1000
ORDER BY price
LIMIT 20;
4.3 Nginx 反向代理
生产环境建议在 PostgREST 前面加 Nginx,处理 HTTPS、限流、CORS:
# /etc/nginx/conf.d/postgrest.conf
upstream postgrest {
server 127.0.0.1:3001;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name api.jsjson.com;
ssl_certificate /etc/letsencrypt/live/api.jsjson.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.jsjson.com/privkey.pem;
# 限流:每 IP 每秒 20 个请求
limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;
location / {
limit_req zone=api burst=40 nodelay;
proxy_pass http://postgrest;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# CORS(也可以在 PostgREST 中配置)
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, POST, PATCH, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Prefer" always;
if ($request_method = OPTIONS) {
return 204;
}
}
}
4.4 与传统后端的性能对比
以下是一个真实场景的对比测试——1000 并发请求,查询带 JOIN 的列表接口:
| 指标 | PostgREST | Express + Prisma | Fastify + Drizzle |
|---|---|---|---|
| P50 延迟 | 3ms | 12ms | 8ms |
| P99 延迟 | 15ms | 45ms | 28ms |
| QPS(单实例) | 12,000 | 3,500 | 5,200 |
| 内存占用 | 25MB | 180MB | 120MB |
| 冷启动时间 | <100ms | 2-5s | 1-3s |
| 代码行数 | 0(配置) | ~800 行 | ~600 行 |
⚡ 关键结论: PostgREST 的性能优势来自「零中间层」——没有 ORM 的对象映射开销、没有 JSON 序列化层(它直接用 PostgreSQL 的 JSON 函数)、没有应用层的 GC 压力。代价是你失去了应用层的灵活性。
💡 五、适用场景与避坑指南
5.1 ✅ 推荐使用场景
- 内部管理后台:CRUD 密集、不需要复杂业务逻辑、需要快速上线
- 原型/MVP:验证想法阶段,需要最快出 API
- 微服务中的数据服务:作为数据层微服务,前端直接调用
- BaaS 替代品:不想用 Firebase/Supabase 但想要类似体验
- 数据开放平台:对外暴露数据查询接口,RLS 控制权限
5.2 ❌ 不适合的场景
- 复杂业务逻辑:大量条件分支、状态机、工作流——PostgreSQL 函数可以写,但维护成本高
- 第三方 API 集成:需要调用外部服务(支付、短信、邮件)——PostgREST 不能直接做
- 实时 WebSocket:PostgREST 支持 PostgreSQL 的
LISTEN/NOTIFY,但功能有限 - 文件上传/处理:需要额外的方案(如 presigned URL)
5.3 ⚠️ 常见坑点
-
外键关系歧义:当两个表之间有多个外键时,PostgREST 不知道用哪个关系。解决方法是在数据库中创建明确的视图或函数。
-
嵌套写入限制:PostgREST 支持嵌套 INSERT,但不支持嵌套 UPDATE。复杂写入需要通过 RPC 函数。
-
NULL 排序行为:PostgreSQL 默认 NULLS LAST,但不同数据库行为不同。迁移时注意。
-
大结果集超时:
db-max-rows限制了单次返回行数,但复杂查询本身也可能超时。需要在 PostgreSQL 层面设置statement_timeout。 -
Schema 变更热加载:PostgREST 默认每 10 秒刷新 schema 缓存。紧急情况下可以发
NOTIFY pgrst, 'reload schema'强制刷新。
🎯 总结
PostgREST 不是银弹,但它是一个被严重低估的工具。对于 CRUD 密集的场景,它能让你从「写 API」变成「设计数据库」——把精力花在数据模型和安全策略上,而不是重复的路由和控制器代码。
我的建议是这样分层使用:
- 纯数据查询(列表、详情、搜索)→ PostgREST 直接出
- 简单业务逻辑(下单、库存扣减)→ PostgreSQL 函数 + PostgREST RPC
- 复杂业务逻辑(支付回调、第三方集成)→ 独立后端服务
- 实时需求 → WebSocket 服务 + PostgreSQL LISTEN/NOTIFY
相关工具推荐:
- 📦 PostgREST 官方文档 — 完整的配置和 API 参考
- 🔧 PostgREST Docker 镜像 — 一键部署
- 🗄️ pgAdmin — 数据库管理工具
- 📊 pg_stat_statements — 查询性能分析
- 🔐 JWT.io — JWT 调试工具
💡 提示: 如果你想要 PostgREST 的能力但又需要管理面板、认证、存储等开箱即用的功能,可以看看 Supabase——它本质上就是 PostgREST + PostgreSQL + 额外服务的一站式方案。