pREST功能简介

August 14, 2024

pREST是一个使用go开发的开源项目,用于为 PostgreSQL 数据库创建实时高性能的 REST 接口,旨在简化和加速基于 PostgreSQL 的应用开发。它允许开发者通过简单的配置将现有的 PostgreSQL 数据库转化为一套完整的 REST 服务,无需自己去实现繁杂的后端CRUD服务。 pREST 的核心在于其直接与 PostgreSQL 数据库交互的能力,利用 Go 语言的并发特性实现高效率。此外,它还支持 JWT 身份验证,提供数据迁移工具,并兼容多种环境,如 Docker 和 Heroku。类似的项目有postgrest

刚好最近在关注一些开源低代码平台,尤其在后台CRUD能力这块一直没有看到纯后端的实现。之前阅读了directus的源码,它在一些复杂关系的处理上感觉很晦涩,不够清晰易懂也缺乏文档说明。刚好看了使用go实现的pREST,所以安装后体验了一把,对相关能力和实现原理有了个大致的了解。

部署

老习惯,还是喜欢本地安装的方式来调试其用法。

  1. 下载仓库并编译。
    Loading...
  2. 配置文件参考 prest.toml
    Loading...
  3. DB创建,复用了之前docker创建的postgres服务,新建了用户以及database。
  4. 创建用户表,供鉴权测试使用。创建用户表完毕后手工在 prest_users 中写入一条记录。
    Loading...
  5. 创建测试表,并写入mock记录。
    Loading...

所有任务完成后即可启动服务。


接口能力

pREST提供了一系列REST的API,可以通过API了解其基础能力。

meta

主要用于拉取一些元数据,包括db、schema以及table列表。可以通过修改配置禁用此类接口。

/databases列举所有db
/schemas列举所有schema
/tables列举所有table
/{DATABASE}/{SCHEMA}列举db+schema下的所有table

返回格式为json,以 /tables 为例:

Loading...

PS:除了手动创建的用户表外(为测试鉴权使用),pREST基本没有其他系统表,类似directus中的collection表、字段配置表以及操作日志等。

auth

用户认证使用,用于获取JWT Token。支持两种方式的认证,注意请求参数携带方式需要与配置文件中的 auth.type 一致。

Loading...

此时会返回jwt token,后续需要鉴权的请求需要带上对应的 Authorization header。

table

Endpoints/show/{DATABASE}/{SCHEMA}/{TABLE}

用于查看某个table的表结构,即各个field字段的详细信息,包括字段名、数据类型,长度限制、is_nullable、is_updatable(是否允许更新?)、default_value、table_schema等。以上面的cars表为例,对应存在3个字段。

Loading...

ps:table_schema 这种表的公共信息不应该存放在field中。类似directus有一个系统表collection存放所有的表基础信息。

此类信息基本也是从postgres的系统表 information_schema.columns 实时查询而来。对应的sql:

Loading...

CRUD

  • 插入记录: POST /{DATABASE}/{SCHEMA}/{TABLE}
  • 更新记录:PUT/PATCH /{DATABASE}/{SCHEMA}/{TABLE}?{FIELD NAME}={VALUE}
  • 删除记录:DELETE /{DATABASE}/{SCHEMA}/{TABLE}?{FIELD NAME}={VALUE}
  • 查询记录:GET /{DATABASE}/{SCHEMA}/{TABLE}

返回插入、更新后的结果数据。查询时拉取表中的所有数据,可以通过查询参数中的各参数组合指定查询条件。

查询参数

查询参数的本质是将API参数转换为pg对应的SQL语句,pREST支持通过参数来制定sql条件。包括

  • 指定查询字段,用于控制select语句后的字段名
  • 指定过滤条件,用于控制where语句中的字段条件。
  • 翻页参数,控制sql语句中的limit、offset等翻页查询场景的参数。
  • 排序参数:控制sql语句中的order字段
  • 分组:控制sql语句中的group by
  • 聚合参数:对应sql中的count、sum等聚合函数。

来看两个具体的例子。

  • 使用distinct查询年份字段,且分页拉取
    Loading...
  • 查询每个品牌brand的车数量
    Loading...

高级查询

operator比较

支持>、<、in、like等各中operator

  • 查询year > 2022 的数据。
Loading...

join

用于连表查询,格式:

Loading...

参数介绍

  • Type: 关联类型
    • inner
    • left
    • right
    • outer
  • Table 用于关联的表
  • Table.field 用于关联的字段
  • Operator: 字段标胶操作符
    • $eq
    • $lt
    • $gt
    • $lte
    • $gte
  • Table2.field - 用于关联表及字段。

来个列子:

Loading...

注意查询语句的构造参数过于复杂,目前仅支持1级关联。如果需要更加复杂的关联,官方建议使用自定义查询

自定义查询

对于一些复杂查询的场景,pREST采用了直接交给用户编写sql的方案来实现自定义查询方式解决。

执行前,先创建一份查询模板文件,文件位置与配置文件中的location参数一致。如 custom_queries/awesome_folder/example_of_cars.read.sql

Loading...

注意需要在location下新建二级目录,因为go路由匹配需要。

在模板文件中有两个参数,需要在API调用时由客户端通过http 参数或者header携带到服务端,拼接成完整的sql。

所以其调用方式大致如下:

Loading...

 

可见自定义查询的本质就是使用模板字符串和外部参数,拼接成完整的SQL语句并执行,来保证最大程度的灵活性。

这样也有一个问题,模板通常是固定死的,比如想在模板中使用in语句,如 where field in ({{bd1}}, {{bd2}}) 。如果参数数量不固定,这样就需要编写多套类似的模板,根据参数数量来调用不同的模板。对于此类问题,pREST也提供了一定的拓展能力。

pREST提供了几个函数来处理动态参数的问题:

Loading...

以defaultOrValue为例,此函数用来提供默认参数,如果客户端没有携带参数bd时,就使用sql中指定的默认参数:

Loading...

这样客户端就可以指定参数或者使用默认参数

Loading...

总结

pREST作为一个纯后台项目,在灵活性上还是非常不错的。虽然仅支持pg数据库,但是查询的基本要素基本都支持了,包括:

  • select字段
  • where条件中的字段以及对应的operation操作:> < == in 等
  • order指定
  • group by分组
  • 聚合函数,包括count、sum、avg、max/min等

可以满足基础的查询功能。另外也支持简单的join需求。

👉🏼
但是,对于复杂的join(比如多级join)查询是无法直接支持的,只能通过自定义查询去写sql解决。本质的难点还是如何将REST参数转换为多级join,在directus的实现中,提供了管理台来给到管理员去主动去配置 1:N 、N:1 等关联关系,降低了普通用户的使用门槛。但是pREST作为一个后台项目,确实提供自定义查询是一个不错的思路。毕竟开发者对自己的数据模型会理解的更加的清楚,也避免去理解类似directus中的Relation关系的成本。

PS:本文所实现的查询http请求存放在了postman,需要的可以在这里fork一份。

See all postsSee all posts