1208 字
6 分钟
PostgREST 入门与实践

PostgREST 是什么#

PostgREST 能把 PostgreSQL 数据库直接封装成符合 RESTful 规范的 API 服务器。你无需再写一层 Node/Go 服务,只要约定好角色权限、视图和函数,它会自动完成:

  • 按照表/视图结构生成标准的 CRUD 接口;
  • 基于 SQL 权限模型实现细粒度的访问控制;
  • 支持过滤、排序、分页、多表关联等查询;
  • 集成 JWT 鉴权用于多租户或行级安全(RLS)。

这篇笔记介绍如何快速搭建、配置 JWT、梳理常用查询操作符,以及一些上线经验。

Docker Compose 快速启动#

version: '3'
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: change_me
    volumes:
      - ./data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  postgrest:
    image: postgrest/postgrest:v12.0.2
    depends_on:
      - db
    ports:
      - "3000:3000"
    environment:
      PGRST_DB_URI: postgres://admin:change_me@db:5432/appdb
      PGRST_DB_SCHEMAS: api
      PGRST_DB_ANON_ROLE: anon
      PGRST_OPENAPI_SERVER_PROXY_URI: http://127.0.0.1:3000
      PGRST_JWT_SECRET: my_super_secret_32_bytes_key

关键环境变量说明:

  • PGRST_DB_URI:连接字符串,至少使用具备 USAGE 权限的用户。
  • PGRST_DB_SCHEMAS:暴露给 API 的 schema,建议单独建 api schema 放视图和函数。
  • PGRST_DB_ANON_ROLE:匿名访问使用的 PostgreSQL role。
  • PGRST_JWT_SECRET:JWT 共享密钥,生产环境请通过 openssl rand -base64 32 生成并写入 .env
TIP

如果部署在反向代理之后,需要把 PGRST_SERVER_PROXY_URIPGRST_OPENAPI_SERVER_PROXY_URI 指向外部访问域名,确保 OpenAPI 文档和重定向正确。

角色与权限模型#

PostgREST 完全依赖 PostgreSQL 的角色/权限体系。推荐的最小结构如下:

-- 匿名角色(仅能 GET)
create role anon nologin;
grant usage on schema api to anon;
grant select on all tables in schema api to anon;

-- 受信任角色(可以写)
create role webuser nologin;
grant usage on schema api to webuser;
grant select, insert, update, delete on all tables in schema api to webuser;

-- 将数据库登录用户映射到这些角色
create role admin login password 'change_me';
grant anon, webuser to admin;
  • 匿名请求默认使用 PGRST_DB_ANON_ROLE
  • 带 JWT 的请求如果包含 role 字段,PostgREST 会尝试切换到目标角色(需确保 token 里的角色是登录角色的成员)。
NOTE

PostgreSQL 默认不会自动把权限应用到新表/视图。可使用 ALTER DEFAULT PRIVILEGES 或数据库迁移工具(如 Sqitch、Flyway)统一维护。

准备 API Schema#

在业务 schema(例如 public)里定义真实表,再在 api schema 中创建视图/函数对外暴露。这样可以隐藏内部字段,并内置联表逻辑。

create schema if not exists api;

create table public.todos (
  id serial primary key,
  user_id int not null,
  title text not null,
  done boolean default false,
  inserted_at timestamptz default now()
);

create view api.todos as
  select id, title, done, inserted_at
  from public.todos;

重载 /todos 接口即可拿到 api.todos 视图结果,user_id 字段不会被暴露。

启用行级安全(RLS)#

alter table public.todos enable row level security;

create policy select_own_todos on public.todos
  for select
  using (current_setting('request.jwt.claim.user_id', true)::int = user_id);

create policy write_own_todos on public.todos
  for all
  using (current_setting('request.jwt.claim.user_id', true)::int = user_id)
  with check (current_setting('request.jwt.claim.user_id', true)::int = user_id);

当请求携带的 JWT 包含 user_id claim 时,PostgREST 会写入 request.jwt.claim.user_id 这个 GUC 变量,策略自然就生效。

生成 JWT#

openssl rand -base64 32
# 复制结果写入 PGRST_JWT_SECRET

示例 payload:

{
  "role": "webuser",
  "user_id": 42
}

将 payload 使用 HS256 与密钥签名后即可调用受保护接口。可以用 jwt.io 生成并调试。

请求示例:

curl https://api.example.com/todos \
  -H "Authorization: Bearer $TOKEN"

查询语法速查#

PostgREST 通过 URL 查询参数映射 SQL 过滤器。核心操作符示例:

操作语法说明
等于?done=eq.trueeq 表示 =
模糊?title=ilike.*bug*ilike 支持大小写不敏感
范围?inserted_at=gte.2025-01-01gte/gt/lte/lt
IN?id=in.(1,2,3)逗号分隔
嵌套 OR?or=(done.eq.true,title.ilike.*urgent*)记得 URL encode
JSONB?meta->>status=eq.active支持 ->->>

分页与排序#

  • 排序:?order=inserted_at.desc
  • 限制:?limit=20
  • 游标翻页:使用 Range 头或 ?offset=20

服务端会在响应头给出 Content-Range

多表嵌套#

PostgREST 支持通过外键自动嵌套资源:

alter table public.todos
  add constraint todos_user_fk foreign key (user_id) references public.users(id);

create view api.users as
  select id, email from public.users;

请求 GET /users?select=id,email,todos(id,title) 会自动返回嵌套 JSON。

调用存储过程#

PostgREST 将 SQL 函数映射为 RPC 接口,例如:

create function api.complete_todo(_id int)
returns public.todos as $$
  update public.todos
     set done = true
   where id = _id
   returning *;
$$ language sql security definer;

调用:POST /rpc/complete_todo,请求体为 {"_id": 1}

函数默认运行在调用者角色下。若需提升权限,请使用 security definer 并仔细设定 search_path

常见排错#

  • 接口 401/403:确认 JWT 是否包含 role,且该角色被登录用户授予;检查 RLS 是否放行。
  • 新建表无法访问:忘记给 api schema 或新表授权,或视图没同步。
  • OpenAPI 缺字段:必须在 api schema 下暴露视图,或在函数设置 stable 属性。
  • CORS 问题:配置反向代理添加 CORS 头,或使用 PGRST_SERVER_PROXY_URI

生产实践建议#

  • 使用迁移脚本统一管理角色、策略和视图,避免手工操作。
  • postgrest 服务加上 healthcheck,配合 Nginx/Traefik 自动探活。
  • 结合 pg_stat_statements 分析慢查询,必要时创建索引或重写视图。
  • 对外暴露前务必在 staging 环境跑一轮权限测试,防止数据泄露。

参考资料#

PostgREST 入门与实践
https://bangwu.top/posts/postgrest/
作者
棒无
发布于
2025-05-29
许可协议
CC BY-NC-SA 4.0