在Better-Auth 1.3.6/1.3.7中更新用户名时遭遇PG错误:users表的displayUsername列不存在
看起来你遇到的问题是Better-Auth在尝试更新用户信息时,试图操作displayUsername这个驼峰命名的列,但你的PostgreSQL用户表中实际存在的是display_username(蛇形命名)列——PostgreSQL对字段命名是严格区分的,所以才会抛出“列不存在”的错误。
问题根源拆解
从你提供的迁移文件能看到,创建users表时你定义的字段是display_username,但Better-Auth内部生成的更新SQL却在引用displayUsername。这明显是字段命名风格不匹配导致的冲突:Better-Auth默认可能采用驼峰命名生成数据库操作语句,而你的数据库Schema用的是蛇形命名,两者没有对齐。
解决方案
下面提供几种可行的解决思路,按推荐程度排序:
方案一:配置Better-Auth适配蛇形命名字段(最推荐)
在Better-Auth的初始化配置中,指定用户表的字段映射,让它把内部使用的驼峰字段名对应到数据库中的蛇形字段名。针对1.3.x版本,你可以尝试这样配置:
// 你的auth初始化文件(比如src/auth.ts) import { auth } from "better-auth"; export const { handlers, signIn, signOut, getSession } = auth({ database: { url: process.env.DATABASE_URL, // 你的数据库连接地址 usersTable: { columns: { // 将Better-Auth内部的驼峰字段映射到数据库的蛇形字段 displayUsername: "display_username", // 如果还有其他类似冲突的字段,也可以在这里添加映射 // 比如 emailVerified: "email_verified" } } }, // 其他Better-Auth配置(如登录 providers、session 配置等) });
如果你的Better-Auth版本没有usersTable.columns这个配置项,可以查看对应版本的官方文档,关键词可以搜「field mapping」「column aliases」来找到正确的配置方式。
方案二:修改数据库Schema适配驼峰字段
如果你不想调整Better-Auth的配置,也可以修改数据库表结构,让字段名和Better-Auth的期望保持一致:
选项A:重命名现有字段(推荐,无冗余)
创建一个新的迁移文件(比如src/data/migrations/1748xxxxxx_rename-display-username.ts):
import type { MigrationBuilder } from "node-pg-migrate"; export const up = (pgm: MigrationBuilder) => { // 将display_username重命名为displayUsername pgm.renameColumn("users", "display_username", "displayUsername"); }; export const down = (pgm: MigrationBuilder) => { // 回滚操作 pgm.renameColumn("users", "displayUsername", "display_username"); };
运行迁移命令(比如npm run migrate up)后,数据库字段名就会和Better-Auth的期望匹配。
选项B:添加新驼峰字段并同步数据(不推荐,有冗余)
如果需要保留原有的display_username字段,可以添加新字段并同步现有数据:
import type { MigrationBuilder } from "node-pg-migrate"; export const up = (pgm: MigrationBuilder) => { // 添加displayUsername列 pgm.addColumn("users", "displayUsername", { type: "text" }); // 同步现有数据到新列 pgm.sql("UPDATE users SET displayUsername = display_username;"); }; export const down = (pgm: MigrationBuilder) => { // 回滚删除新列 pgm.dropColumn("users", "displayUsername"); };
⚠️ 注意:这种方式会产生冗余字段,后续维护需要同步两个字段的值,所以仅作为临时方案。
方案三:检查Better-Auth的全局命名风格配置
部分Better-Auth版本提供了全局命名风格开关,比如是否自动将驼峰字段转换为蛇形命名。你可以查看1.3.6/1.3.7版本的文档,看看是否有类似snakeCase: true这样的配置项,开启后就能自动适配你的数据库Schema。
你提供的错误信息与迁移文件参考
完整错误信息
SERVER_ERROR: error: column "displayUsername" of relation "users" does not exist at /Users/vr/Desktop/code/ch_NEXT/ch_api/node_modules/pg/lib/client.js:545:17 at process.processTicksAndRejections (node:internal/process/task_queues:105:5) at async PostgresConnection.executeQuery (/Users/vr/Desktop/code/ch_NEXT/ch_api/node_modules/kysely/dist/cjs/dialect/postgres/postgres-driver.js:93:49) at async /Users/vr/Desktop/code/ch_NEXT/ch_api/node_modules/kysely/dist/cjs/query-executor/query-executor-base.js:37:28 at async DefaultConnectionProvider.provideConnection (/Users/vr/Desktop/code/ch_NEXT/ch_api/node_modules/kysely/dist/cjs/driver/default-connection-provider.js:12:20) at async DefaultQueryExecutor.executeQuery (/Users/vr/Desktop/code/ch_NEXT/ch_api/node_modules/kysely/dist/cjs/query-executor/query-executor-base.js:36:16) at async UpdateQueryBuilder.execute (/Users/vr/Desktop/code/ch_NEXT/ch_api/node_modules/kysely/dist/cjs/query-builder/update-query-builder.js:461:24) at async UpdateQueryBuilder.executeTakeFirst (/Users/vr/Desktop/code/ch_NEXT/ch_api/node_modules/kysely/dist/cjs/query-builder/update-query-builder.js:477:26) at async withReturning (/Users/vr/Desktop/code/ch_NEXT/ch_api/node_modules/better-auth/dist/shared/better-auth.DOgvYMa8.cjs:119:13) at async Object.update (/Users/vr/Desktop/code/ch_NEXT/ch_api/node_modules/better-auth/dist/shared/better-auth.DOgvYMa8.cjs:265:16) at PostgresConnection.executeQuery (/Users/vr/Desktop/code/ch_NEXT/ch_api/node_modules/kysely/dist/cjs/dialect/postgres/postgres-driver.js:105:69) at process.processTicksAndRejections (node:internal/process/task_queues:105:5) at async /Users/vr/Desktop/code/ch_NEXT/ch_api/node_modules/kysely/dist/cjs/query-executor/query-executor-base.js:37:28 at async DefaultConnectionProvider.provideConnection (/Users/vr/Desktop/code/ch_NEXT/ch_api/node_modules/kysely/dist/cjs/driver/default-connection-provider.js:12:20) at async DefaultQueryExecutor.executeQuery (/Users/vr/Desktop/code/ch_NEXT/ch_api/node_modules/kysely/dist/cjs/query-executor/query-executor-base.js:36:16) at async UpdateQueryBuilder.execute (/Users/vr/Desktop/code/ch_NEXT/ch_api/node_modules/kysely/dist/cjs/query-builder/update-query-builder.js:461:24) at async UpdateQueryBuilder.executeTakeFirst (/Users/vr/Desktop/code/ch_NEXT/ch_api/node_modules/kysely/dist/cjs/query-builder/update-query-builder.js:477:26) at async withReturning (/Users/vr/Desktop/code/ch_NEXT/ch_api/node_modules/better-auth/dist/shared/better-auth.DOgvYMa8.cjs:119:13) at async Object.update (/Users/vr/Desktop/code/ch_NEXT/ch_api/node_modules/better-auth/dist/shared/better-auth.DOgvYMa8.cjs:265:16) at async Object.update (/Users/vr/Desktop/code/ch_NEXT/ch_api/node_modules/better-auth/dist/shared/better-auth.DzBLnNed.cjs:463:19) { length: 135, severity: 'ERROR', code: '42703', detail: undefined, hint: undefined, position: '37', internalPosition: undefined, internalQuery: undefined, where: undefined, schema: undefined, table: undefined, column: undefined, dataType: undefined, constraint: undefined, file: 'analyze.c', line: '2536', routine: 'transformUpdateTargetList' }
迁移文件:1748345325030_create-users-table.ts
import type { MigrationBuilder } from "node-pg-migrate"; export const up = (pgm: MigrationBuilder) => { pgm.createTable( "users", { id: { primaryKey: true, type: "uuid" }, ban_expires: { type: "timestamptz" }, ban_reason: { type: "text" }, banned: { type: "boolean" }, display_username: { type: "text" }, email: { notNull: true, type: "text" }, email_verified: { notNull: true, type: "boolean" }, image: { type: "text" }, name: { notNull: true, type: "text" }, role: { type: "text" }, username: { type: "text" }, created_at: { notNull: true, type: "timestamptz" }, updated_at: { notNull: true, type: "timestamptz" }, }, { ifNotExists: true } ); }; export const down = (pgm: MigrationBuilder) => { pgm.dropTable("users", { cascade: true, ifExists: true }); };
迁移文件:1748348413644_add-users-indexes.ts
import type { MigrationBuilder } from "node-pg-migrate"; export const up = (pgm: MigrationBuilder) => { pgm.createIndex("users", "email", { ifNotExists: true, method: "btree", name: "users_email_idx", unique: true, }); pgm.createIndex("users", "username", { ifNotExists: true, method: "btree", name: "users_username_idx", unique: true, }); }; export const down = (pgm: MigrationBuilder) => { pgm.dropIndex("users", "username", { cascade: true, ifExists: true, name: "users_username_idx", }); // 建议补充删除email索引的回滚操作 // pgm.dropIndex("users", "email", { // cascade: true, // ifExists: true, // name: "users_email_idx", // }); };
内容来源于stack exchange

