Payload + Vercel Postgres 数据库适配器完整指南:安装、连接配置与源码级原理
【免费下载链接】payloadPayload is the open-source, fullstack Next.js framework, giving you instant backend superpowers. Get a full TypeScript backend and admin panel instantly. Use Payload as a headless CMS or for building powerful applications.项目地址: https://gitcode.com/GitHub_Trending/pa/payload
本文围绕 packages/db-vercel-postgres/README.md 展开,全面讲解 Payload 官方 Vercel Postgres 适配器
@payloadcms/db-vercel-postgres的安装、两种连接字符串配置方式(显式指定与自动探测)、关键参数,并结合仓库内 适配器入口源码、连接逻辑 与 类型定义,深入揭示其"本地用 pg、生产用 Vercel 连接池"的双模式运行机制,以及在 Vercel 上从模板到生产的完整落地步骤。
一、Vercel Postgres 适配器是什么
@payloadcms/db-vercel-postgres是 Payload 官方提供的、面向 Vercel Postgres,其核心代码复用了@payloadcms/drizzle与@payloadcms/drizzle/postgres提供的全部数据库操作原语,再叠加@vercel/postgres的连接能力。
从包依赖看,该适配器构建在 Drizzle ORM 之上(package.json 中声明了drizzle-orm、drizzle-kit、pg、@vercel/postgres等依赖),因此它继承了 Drizzle 对 Postgres 的完整表达能力。而对外暴露时,它遵循 Payload 的统一数据库适配器契约——vercelPostgresAdapter()返回的是一个带有name、init、defaultIDType等字段的DatabaseAdapterObj对象,最终交由payload.config.ts中的db字段使用。
也就是说:对 Payload 上层而言,接入 Vercel Postgres 与接入普通 Postgres 几乎无异;差异集中在底层连接客户端的选择与数据库的托管位置上。
二、安装
在你的 Payload 项目根目录安装该适配器即可:
npm install @payloadcms/db-vercel-postgres仓库中使用pnpm工作区开发,因此 monorepo 内声明为"@payloadcms/db-vercel-postgres": "workspace:*"。作为 peer dependency,payload本体需已存在于项目中。
值得留意的是,适配器的package.json暴露了多个子路径(subpath exports),除了主入口外还包括:
@payloadcms/db-vercel-postgres/migration-utils:提供数据库迁移辅助工具;@payloadcms/db-vercel-postgres/drizzle:直接转发drizzle-orm(见 drizzle-proxy/index.ts,内容仅为export * from 'drizzle-orm');- 以及
./drizzle/pg-core、./drizzle/node-postgres、./drizzle/relations等 Drizzle 代理子路径。
这些子路径主要是为了在自定义数据库操作或生成 schema 文件时,让 Payload 生态内部及开发者能够稳定引用到与当前 Drizzle 版本完全一致的依赖,避免版本错位。
三、在 Payload 配置中使用:两种连接方式
适配器的用法是在payload.config.ts的buildConfig中把它传给db字段。原版 README 给出了两种方式,下面结合仓库实现分别说明。
3.1 方式一:显式传入连接字符串
import { buildConfig } from 'payload' import { vercelPostgresAdapter } from '@payloadcms/db-vercel-postgres' export default buildConfig({ db: vercelPostgresAdapter({ pool: { connectionString: process.env.DATABASE_URL, }, }), // ...rest of config })连接信息通过pool.connectionString提供。从 connect.ts 的实现可以看到:
const connectionString = this.poolOptions?.connectionString ?? process.env.POSTGRES_URL即poolOptions.connectionString(pool参数就是poolOptions)拥有最高优先级;若未传,则自动回退读取process.env.POSTGRES_URL。这正是方式二的实现基础。
3.2 方式二:由 Vercel 自动探测环境变量
import { buildConfig } from 'payload' import { vercelPostgresAdapter } from '@payloadcms/db-vercel-postgres' export default buildConfig({ db: vercelPostgresAdapter(), // ...rest of config })不传任何参数时,适配器在 connect.ts 中会直接使用@vercel/postgres导出的默认sql客户端:
client = this.poolOptions ? new VercelPool(this.poolOptions) : sql而@vercel/postgres的默认sql会自动从 Vercel 平台注入的POSTGRES_URL(以及配套的POSTGRES_URL_NON_POOLING、POSTGRES_USER等)中读取连接信息。因此,只要在 Vercel 上给项目关联了 Vercel Postgres(或 Neon)存储服务,本地payload.config.ts里甚至可以完全不写任何连接细节,构建与部署时即自动生效。
3.3 关键行为:本地 vs 生产客户端自动切换
这是理解该适配器最重要的一环。在 connect.ts 中,客户端的选择逻辑为:
if ( !this.forceUseVercelPostgres && connectionString && ['127.0.0.1', 'localhost'].includes(new URL(connectionString).hostname) ) { client = new pg.Pool(...) // 本地数据库 → 用 pg 模块 } else { client = this.poolOptions ? new VercelPool(this.poolOptions) : sql // 否则用 Vercel 连接池 }即默认情况下:
- 当连接字符串的主机名是
127.0.0.1或localhost时,适配器自动改用原生pg.Pool连接本地 Postgres,原因是@vercel/postgres不支持连接本地数据库; - 其他场景(远程 Vercel / Neon 托管数据库)则使用
VercelPool或@vercel/postgres的默认sql连接池。
模板仓库的 README 也明确记录了这一行为(见 templates/with-vercel-postgres/README.md):如果连接字符串包含 localhost 或 127.0.0.1,代码将自动改用普通 Postgres 适配器而不是 Vercel。
如果你希望本地开发时也强制走@vercel/postgres客户端,可以把参数forceUseVercelPostgres设为true,但此时需要参考 Vercel 文档用 Neon 提供的 Docker Compose 方案启动一个特殊配置的本地 Postgres 实例(Vercel 官方"Local Development"文档中 Option 2 的做法),相关说明也记录在 types.ts 的forceUseVercelPostgres注释中。
四、参数总览(Args)
适配器接收一个Args对象,完整类型定义见 types.ts。下面是按用途归类整理的关键参数:
4.1 连接相关
| 参数 | 类型 | 说明 |
|---|---|---|
pool | VercelPostgresPoolConfig | Vercel Postgres 连接池配置,可传connectionString等。不传时由@vercel/postgres自动读取 Vercel 环境变量 |
connectionString | string | 顶层保留字段(与pool.connectionString语义一致的兼容入口) |
forceUseVercelPostgres | boolean | 默认false。为true时即使连接localhost也强制使用@vercel/postgres客户端 |
readReplicas | string[] | 只读副本连接字符串数组,用于读写分离 |
readReplicasAfterWriteInterval | number | 写入后多久内继续把读请求路由到主库,默认2000(毫秒),防止复制延迟导致读到旧数据 |
4.2 Schema 结构与字段命名
| 参数 | 类型 | 说明 |
|---|---|---|
idType | 'serial' \| 'uuid' \| 'uuidv7' | 主键类型,默认'serial',对应 Payload 侧类型number;选 uuid 系列则 Payload 侧为text |
blocksAsJSON | boolean | 默认false。为true时把 blocks 字段以 JSON 列存储,而非关系型结构 |
schemaName | string | 使用的 Postgres schema 名(实验性)。传参时内部会调用pgSchema(schemaName)(见 index.ts) |
localesSuffix | string | 本地化(localization)字段表后缀,默认_locales |
relationshipsSuffix | string | 关系表后缀,默认_rels |
versionsSuffix | string | 版本表后缀,默认_v |
extensions | string[] | 需要启用的 Postgres 扩展列表(如vector、unaccent等),连接成功后会调用createExtensions()安装 |
4.3 迁移与开发期行为
| 参数 | 类型 | 说明 |
|---|---|---|
migrationDir | string | 迁移文件目录,默认由findMigrationDir从 package.json 位置推导 |
prodMigrations | { name, up, down }[] | 生产环境直接内联执行的迁移数组。连接逻辑中,当NODE_ENV === 'production'且存在prodMigrations时自动执行(见 connect.ts) |
push | boolean | 是否在非生产环境自动把 Drizzle schema 推送到数据库(dev schema push) |
disableCreateDatabase | boolean | 默认false。连接发现database ... does not exist时是否禁止自动建库 |
generateSchemaOutputFile | string | 执行payload generate:db-schema时生成 Drizzle schema 文件的输出路径 |
4.4 高级自定义
| 参数 | 类型 | 说明 |
|---|---|---|
afterSchemaInit/beforeSchemaInit | PostgresSchemaHook[] | schema 构建完成前/后挂钩,可用于注入 Payload 不直接支持的建表能力(如复合索引、生成列、vector 等)或保留既有数据库结构 |
query | PostgresQueryConfig | 自定义 Payload 查询运算符(contains、like、not_like等)的实现,典型场景是用postgresUnaccent()让文本匹配不区分重音 |
transactionOptions | false \| PgTransactionConfig | 传false表示禁用事务支持;传对象则作为事务配置透传给 Drizzle |
allowIDOnCreate | boolean | 默认false。开启后允许在create时自行指定文档 ID(如payload.create({ data: { id: 1, ... } })) |
logger | DrizzleConfig['logger'] | 透传给 Drizzle 的 SQL 日志器 |
在 index.ts 中可以看到适配器入口先解析几个影响全局的默认值:idType缺省为'serial',对应 Payload 侧默认 ID 类型为number;allowIDOnCreate缺省为false。
五、连接生命周期与初始化原理
connect是适配器最重要的运行时入口。综合 index.ts 与 connect.ts,一次完整的数据库连接过程大致如下:
- 解析连接参数:优先
poolOptions.connectionString,否则回退process.env.POSTGRES_URL; - 选定客户端:本地地址用
pg.Pool,否则用VercelPool/默认sql; - 构造 Drizzle 实例:通过
drizzle({ client, logger, schema })把客户端包装成 Drizzle 数据库对象,并赋值给适配器的this.drizzle(见 connect.ts); - 配置读副本:若传了
readReplicas,则为主库包装withReplicas(),实现读写分离,写后readReplicasAfterWriteInterval(默认 2000ms)内读主库; - 可选删库:当环境变量
PAYLOAD_DROP_DATABASE === 'true'且非热重载时,先执行dropDatabase()清空 schema(开发/CI 场景用于重置数据); - 容错建库:捕获
database ... does not exist类错误,若未禁用自动建库则调用createDatabase()创建后再重连(connect.ts); - 初始化扩展与运算符校验:调用
createExtensions()安装声明过的扩展,并用assertOperatorHandlerExtensionsInstalled()校验自定义运算符所需扩展已就位; - 开发期 schema 推送:当
NODE_ENV !== 'production'、PAYLOAD_MIGRATING !== 'true'且push非false时,自动执行pushDevSchema()把最新的 collection/字段结构同步到本地库(connect.ts)。这就是"本地改字段即时生效"的来源; - 生产迁移执行:若
NODE_ENV === 'production'且配置了prodMigrations,则在启动阶段自动执行这些迁移; - 标记就绪:通过
resolveInitializing()解除初始化锁,供上层等待。
值得注意的工程细节是,vercelPostgresAdapter()在 index.ts 中通过createDatabaseAdapter()一次性注册了 Payload 所需的全部底层操作——find、create、update、delete、count、事务(beginTransaction/commitTransaction/rollbackTransaction)、草稿与版本查询(queryDrafts/findVersions)、迁移全套命令(migrate/migrateDown/migrateFresh等)——其中绝大多数直接复用@payloadcms/drizzle与@payloadcms/drizzle/postgres的通用实现。这意味着,Vercel Postgres 适配器并没有为每个 CRUD 单独造轮子,而是专注解决"连接层 + 能力注册"这两件事。
六、从零到 Vercel:模板实战与迁移流程
仓库内的 templates/with-vercel-postgres 是一个"bare minimum"的完整可部署模板(包含 Users 认证集合与 Media 上传集合),其 payload.config.ts 给出了真实项目中的标准写法:
db: vercelPostgresAdapter({ pool: { connectionString: process.env.POSTGRES_URL || '', }, }),下面梳理它的完整使用路径,方便你对照落地。
6.1 本地开发
- 在 Vercel 创建项目并关联存储:该模板需要 Vercel Postgres(Neon,托管数据)与 Vercel Blob Storage(托管图片等文件),连接后 Vercel 会自动注入环境变量;
- 本地
.env中加入 Vercel 项目提供的POSTGRES_URL与BLOB_READ_WRITE_TOKEN; - 运行
pnpm install && pnpm dev,访问http://localhost:3000,首次进入按引导创建管理员账号,之后可在 dashboard 点击 Seed 按钮导入示例内容。
本地连接串若指向localhost/127.0.0.1,则按上文所述自动切换到原生pg客户端;若想用真实 Vercel 远程库做本地开发,也可直接使用其POSTGRES_URL。
若希望用 Docker 跑本地 Postgres,可将.env的POSTGRES_URL改为postgres://postgres@localhost:54320/<dbname>,并把 docker-compose.yml 中的POSTGRES_DB设置为同名数据库,然后docker-compose up。
6.2 模式变更与迁移
Postgres 有严格 schema,和 MongoDB 适配器相比多一步"迁移"。模板 README 的建议是:
- 开发期依赖适配器默认的
push: true自动同步字段结构(仅在非生产、非迁移环境生效),因此增删字段可以即时反映; - 一旦数据库被指向生产,应显式把
push设为false,防止意外覆盖生产结构; - 正式上线前,需要把 schema 变更固化成迁移文件:
pnpm payload migrate:create- 服务器构建后、启动生产进程前执行迁移:
pnpm payload migratepayload migrate会把未执行的迁移依次应用,并在数据库中记录迁移历史,实现可追溯、可回滚(migrateDown)的版本化管理。迁移相关的底层命令在适配器注册时即已通过 index.ts 接入 Payload 的迁移工具链。
6.3 Vercel 部署
Vercel 部署会自动注入POSTGRES_URL等连接变量,适配器在无参情况下即可自动识别。构建流程中可执行payload migrate(构建期执行、先于应用启动),确保生产环境始终运行与代码匹配的 schema。
七、自定义查询运算符与常见调优
适配器通过query参数支持自定义运算符实现。比如想让文本匹配对重音不敏感,可以在 index.ts 中看到适配器额外导出了postgresUnaccent与geometryColumn等 Postgres 专用工具函数,这正是配合自定义查询配置使用的:
import { vercelPostgresAdapter, postgresUnaccent } from '@payloadcms/db-vercel-postgres' import { sql } from '@payloadcms/db-vercel-postgres' vercelPostgresAdapter({ query: { // 示例:让 like 等文本运算符基于 unaccent 比较 // 具体结构以 PostgresQueryConfig 类型为准 }, extensions: ['unaccent'], })说明:使用
postgresUnaccent()通常需要数据库启用unaccent扩展,可在extensions数组中声明。自定义运算符的校验由 connect.ts 中的assertOperatorHandlerExtensionsInstalled在连接期自动完成,缺少所需扩展会给出明确报错。
此外,在 types.ts 中还给出了面向未来/边缘场景的能力,例如afterSchemaInit/beforeSchemaInit钩子可用于保留已有数据库结构(配合 Drizzle Kit 的 introspection 从数据库反向生成 schema)或注入 Payload 原生不支持的索引类型。
八、总结
@payloadcms/db-vercel-postgres是一个"薄连接、厚复用"的适配器:连接层由它自主决策(本地pgvs 远程 Vercel 连接池的自动切换),数据操作层则完整复用@payloadcms/drizzle的 PostgreSQL 实现,并以payload db字段的统一契约接入 Payload 生态。对开发者而言,只需理解两点:
- 连接配置:
pool.connectionString显式指定,或省略参数由 Vercel 环境变量自动探测(POSTGRES_URL); - 模式管理:本地开发靠
push自动同步,生产部署靠payload migrate:create+payload migrate固化。
配合 templates/with-vercel-postgres 模板与 docs/database/overview.mdx、docs/database/migrations.mdx 等官方文档,你可以在一小时内完成"本地开发 → 迁移固化 → Vercel 一键部署"的完整闭环。
【免费下载链接】payloadPayload is the open-source, fullstack Next.js framework, giving you instant backend superpowers. Get a full TypeScript backend and admin panel instantly. Use Payload as a headless CMS or for building powerful applications.项目地址: https://gitcode.com/GitHub_Trending/pa/payload
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考