基于PocketBasev0.22.2
。
PocketBase 允许使用 Go 和 JavaScript 扩展其服务器端能力,主要是为了在提供核心功能的同时,兼顾性能、控制力与开发便捷性、灵活性。
PocketBase 内置了一个 JavaScript 引擎,它作为现有 Go API 的可插拔封装层。这意味着开发者可以使用 JavaScript 编写服务器端逻辑,而无需学习 Go 语言。
由于 JavaScript 虚拟机(VM)镜像了 Go API,即使未来项目遇到性能瓶颈或需要更精细的控制,开发者也可以逐步从 JavaScript 迁移到 Go,而无需进行大量代码更改。
基于 Go 扩展 PocketBase
PocketBase 底层使用 echo v5 作为 web 框架,在扩展 PocketBase 的路由时使用的也是 echo 的 API 。
1 | mkdir custom-pocketbase && cd $_ # 新建一个开发目录 |
main.go
1 | package main |
编辑完 main.go
后,开始打包构建应用
1 | go mod tidy # 清理/安装依赖 |
运行程序,执行 ./custom_pocketbase serve
,步骤与 PocketBase 安装配置一样。
PocketBase 的 6 个应用级事件钩子
OnBeforeBootstrap
OnBeforeBootstrap
钩子在主应用程序资源(如:数据库连接、初始设置加载)初始化之前触发。
- 适合 执行一些数据库连接前的配置,或者加载一些全局的、不依赖于网络请求的配置。
- 不适合 执行耗时过长或可能阻塞应用程序启动的操作。
OnAfterBootstrap
OnAfterBootstrap
钩子在主应用程序资源(如:数据库连接、初始设置加载)初始化之后触发。
OnBeforeServe
OnBeforeServe
钩子在内部路由器(echo)开始提供服务之前触发。
- 适合 调整 echo 选项、附加新路由或者路由中间件,这是定义自定义 API 端点或修改现有请求处理流程的理想时机。
OnBeforeApiError
OnBeforeApiError
钩子在向客户端发送错误 API 响应之前触发,允许你进一步修改错误数据或返回一个完全不同的 API 响应。
OnAfterApiError
OnAfterApiError
钩子在向客户端发送错误 API 响应之后立即触发。它可用于将最终的 API 错误记录到外部服务中。
OnTerminate
OnTerminate
钩子在应用程序终止过程中触发(例如,收到 SIGTERM
信号时)。
- 适合 执行清理操作,如关闭文件句柄、释放资源或保存临时状态。
- 不适合 需要保证完成或需要大量处理时间的操作,因为应用程序可能会在不等待钩子完成的情况下突然终止。
基于 JavaScript 扩展 PocketBase
要使用 JavaScript 扩展 PocketBase,需要先有一个 PocketBase 可执行文件,可以从 Github Release 下载,也可以按照上面 基于 Go 扩展 PocketBase 自定义后(也许你就不需要 JS 了:p)构建一个可执行文件。
在 PocketBase 可执行文件旁边创建 pb_hooks
目录,在 pb_hooks
目录内创建 *.pb.js
文件,如:
pb_hooks/main.pb.js
1 | routerAdd("GET", "/hello/:name", (c) => { |
运行程序,执行:./pocketbase serve
。
对于大部分功能,JavaScript API 源自 Go API,但存在两个主要区别:
- 命名约定转换:Go 中导出的方法、字段名称改称半驼峰形式。如:
app.Dao().FindRecordById("example", "RECORD_ID")
变成$app.dao().findRecordById("example", "RECORD_ID")
。 - 异常处理机制:异常在 JavaScript 中会作为常规的 JavaScript 异常被抛出,而不是像 Go 那样作为返回值返回。
全局对象
一些常用的全局对象:
__hooks
:应用pb_hooks
的绝对路径。$app
: 当前运行的 PocketBase 应用实例。$apis.*
:API 路由帮助函数和中间件。$os.*
:系统层级的操作(如:删除目录、执行 shell 命令等)。$security.*
: 底层的帮助函数,用于创建解析 JTW、生成随机数、AES 加密等。- 更多:JSVM 文档
TypeScript 类型声明与代码补全
PocketBase 会在运行目录下的 pb_data
目录下生成 types.d.ts
文件,可在支持 TypeScript LSP 的编辑器/IDE 中指向该类型声明文件来获取相关的文档提示和代码补全。
1 | /// <reference path="../pb_data/types.d.ts" /> |
注意事项与限制
处理器作用域
每个处理器函数(钩子、路由、中间件等)都被序列化并在其 独立的上下文 中作为一个单独的“程序”执行。这意味着你无法访问在处理器作用域之外声明的自定义变量和函数。例如,以下代码不会打印出 test
:
1 | const name = "test" |
解决方案可以通过模块化来解决,当前只支持 CJS 模块化,ESM 模块化方式需用过编译转化后才可用。
pb_hooks/utils.js
1 | module.exports = { |
pb_hooks/main.pb.js
1 | onAfterBootstrap((e) => { |
模块载入
可以通过指定具体的模块文件路径或者模块名来加载模块,搜索模块的规则如下:
- 在当前工作目录下搜索
- 在任意的
node_modules
目录下搜索 - 最近的
package.json
文件的父级目录下的node_modules
目录