创建数据库适配器
了解如何为 Better-Auth 创建自定义数据库适配器
了解如何使用 createAdapterFactory 为 Better-Auth 创建自定义数据库适配器。
我们的 createAdapterFactory 函数设计得非常灵活,我们尽力让它易于理解和使用。
我们希望您可以专注于编写数据库逻辑,而无需担心适配器如何与 Better-Auth 配合工作。
从自定义模式配置、自定义 ID 生成、安全 JSON 解析、键映射、连接查询等,都由 createAdapterFactory 函数处理。
您只需提供数据库逻辑,createAdapterFactory 函数将处理剩余工作。
快速开始
准备工作
- 导入
createAdapterFactory。 - 创建表示适配器配置选项的
CustomAdapterConfig接口。 - 创建适配器!
import { createAdapterFactory, type DBAdapterDebugLogOption } from "better-auth/adapters";
// 你的自定义适配器配置选项
interface CustomAdapterConfig {
/**
* 帮助你调试适配器问题。
*/
debugLogs?: DBAdapterDebugLogOption;
/**
* 模式中的表名是否为复数形式。
*/
usePlural?: boolean;
}
export const myAdapter = (config: CustomAdapterConfig = {}) =>
createAdapterFactory({
// ...
});配置适配器
config 对象主要用于向 Better-Auth 提供关于适配器的信息。
我们尽量减少您在适配器函数中需要编写的代码量,而这些 config 选项帮助我们实现这一点。
// ...
export const myAdapter = (config: CustomAdapterConfig = {}) =>
createAdapterFactory({
config: {
adapterId: "custom-adapter", // 适配器的唯一标识符。
adapterName: "Custom Adapter", // 适配器名称。
usePlural: config.usePlural ?? false, // 模式中的表名是否为复数形式。
debugLogs: config.debugLogs ?? false, // 是否启用调试日志。
supportsJSON: false, // 数据库是否支持 JSON。(默认:false)
supportsDates: true, // 数据库是否支持日期。(默认:true)
supportsBooleans: true, // 数据库是否支持布尔值。(默认:true)
supportsNumericIds: true, // 数据库是否支持自增数字 ID。(默认:true)
},
// ...
});创建适配器
adapter 函数是你编写与数据库交互代码的地方。
// ...
export const myAdapter = (config: CustomAdapterConfig = {}) =>
createAdapterFactory({
config: {
// ...
},
adapter: ({}) => {
return {
create: async ({ data, model, select }) => {
// ...
},
update: async ({ data, model, select }) => {
// ...
},
updateMany: async ({ data, model, select }) => {
// ...
},
delete: async ({ data, model, select }) => {
// ...
},
// ...
};
},
});了解更多关于 adapter 的内容请点击这里。
适配器
adapter 函数是你编写与数据库交互代码的地方。
如果你还没有,建议先查看配置部分中的 options 对象,它对你的适配器可能非常有用。
在深入适配器函数之前,让我们先了解可用的参数。
options:Better Auth 的选项。schema:用户 Better Auth 实例的模式。debugLog:调试日志函数。getFieldName:获取数据库中字段转换后的名称的函数。getModelName:获取数据库中模型转换后的名称的函数。getDefaultModelName:从模式中获取默认模型名称的函数。getDefaultFieldName:从模式中获取默认字段名称的函数。getFieldAttributes:获取特定模型和字段的字段属性的函数。transformInput:保存到数据库之前转换输入数据的函数。transformOutput:从数据库取出后转换输出数据的函数。transformWhereClause:转换数据库查询的 where 条件的函数。
adapter: ({
options,
schema,
debugLog,
getFieldName,
getModelName,
getDefaultModelName,
getDefaultFieldName,
getFieldAttributes,
transformInput,
transformOutput,
transformWhereClause,
}) => {
return {
// ...
};
};适配器方法
- 所有的
model值已根据最终用户的模式配置,转换为数据库正确的模型名称。- 这也意味着如果你需要访问给定模型的
schema版本,你不能直接使用此model值,需要使用getDefaultModelName函数将model转换为schema版本。
- 这也意味着如果你需要访问给定模型的
- 我们会自动根据用户的
schema配置,填充你返回的任何缺失字段。 - 任何包含
select参数的方法,仅用于更高效地从数据库获取数据。你无需担心严格按照select参数返回数据,我们会帮你处理。
create 方法
create 方法用于在数据库中新建记录。
可以向 create 方法传递 forceAllowId 参数,允许在 data 对象中提供 id。
我们会内部处理 forceAllowId,你无需担心。
参数:
model:将插入新数据的模型/表名称。data:插入数据库的数据。select:从数据库返回的字段数组。
确保返回插入到数据库中的数据。
create: async ({ model, data, select }) => {
// 插入数据的示例
return await db.insert(model).values(data);
};update 方法
update 方法用于更新数据库中的一条记录。
参数:
model:将更新记录的模型/表名称。where:更新记录的筛选条件。update:用来更新记录的数据。
确保返回被更新的整条数据,包括未变更的字段。
update: async ({ model, where, update }) => {
// 更新数据的示例
return await db.update(model).set(update).where(where);
};updateMany 方法
updateMany 方法用于批量更新数据库中的多条记录。
参数:
model:将更新记录的模型/表名称。where:更新记录的筛选条件。update:用来更新记录的数据。
updateMany: async ({ model, where, update }) => {
// 批量更新数据的示例
return await db.update(model).set(update).where(where);
};delete 方法
delete 方法用于删除数据库中的一条记录。
参数:
model:将删除记录的模型/表名称。where:删除记录的筛选条件。
delete: async ({ model, where }) => {
// 删除记录的示例
await db.delete(model).where(where);
}deleteMany 方法
deleteMany 方法用于批量删除数据库中的多条记录。
参数:
model:将删除记录的模型/表名称。where:删除记录的筛选条件。
deleteMany: async ({ model, where }) => {
// 批量删除记录的示例
return await db.delete(model).where(where);
};findOne 方法
findOne 方法用于查找数据库中的单条记录。
参数:
model:查找记录的模型/表名称。where:查找记录的筛选条件。select:返回字段的筛选条件。join:可选的连接配置,用于在单次查询中获取关联记录。
findOne: async ({ model, where, select, join }) => {
// 查找单条记录的示例
return await db.select().from(model).where(where).limit(1);
};findMany 方法
findMany 方法用于查找数据库中的多条记录。
参数:
model:查找记录的模型/表名称。where:查找记录的筛选条件。limit:返回记录的数量限制。sortBy:排序条件。offset:偏移数量。join:可选的连接配置,用于在单次查询中获取关联记录。
确保返回查询到的数据数组。
findMany: async ({ model, where, limit, sortBy, offset, join }) => {
// 查找多条记录的示例
return await db
.select()
.from(model)
.where(where)
.limit(limit)
.offset(offset)
.orderBy(sortBy);
};count 方法
count 方法用于统计数据库中记录的数量。
参数:
model:统计记录的模型/表名称。where:统计记录的筛选条件。
count: async ({ model, where }) => {
// 统计记录数的示例
return await db.select().from(model).where(where).count();
};options(可选)
options 对象用于传递自定义适配器配置中可能包含的任何配置。
const myAdapter = (config: CustomAdapterConfig) =>
createAdapterFactory({
config: {
// ...
},
adapter: ({ options }) => {
return {
options: config,
};
},
});createSchema(可选)
createSchema 方法允许 Better Auth CLI 为数据库生成模式。
参数:
tables:用户 Better-Auth 实例模式中的表,预期生成进模式文件。file:用户传入generate命令的期望模式文件输出路径。
createSchema: async ({ file, tables }) => {
// ... 为数据库自定义生成模式的逻辑。
};测试你的适配器
我们提供了一个通过 @better-auth/test-utils 包的测试套件,你可以用它来测试你的适配器。它需要你使用 vitest。
首先安装测试工具:
npm install -D @better-auth/test-utils然后使用 testAdapter 和 createTestSuite 创建测试文件:
import { testAdapter, createTestSuite } from "@better-auth/test-utils/adapter";
import { myAdapter } from "./my-adapter";
const normalTestSuite = createTestSuite("Normal", ({ test, adapter }) => [
test("should create and find a user", async () => {
// 在这里使用 adapter 实例编写你的适配器测试
}),
]);
const { execute } = await testAdapter({
adapter: (options) => {
return myAdapter(/* 你的适配器配置 */);
},
runMigrations: async (options) => {
// 在这里运行数据库迁移
},
tests: [
normalTestSuite(),
],
async onFinish() {
// 可选:所有测试完成后的清理(例如删除数据库文件)
},
});
execute();testAdapter 函数为你处理测试生命周期,包括在测试前运行迁移和测试完成后清理所有表。
在 v1.4 版本中,适配器测试使用了 better-auth/adapters/test 中的 runAdapterTest 和 runNumberIdAdapterTest,该导出在 v1.5 中已移除。请改用 @better-auth/test-utils/adapter。
配置
config 对象用于向 Better-Auth 提供关于适配器的信息。
我们强烈建议认真阅读以下各项配置选项,它们将帮助你正确配置适配器。
必需配置
adapterId
适配器的唯一标识符。
adapterName
适配器的名称。
可选配置
supportsNumericIds
数据库是否支持数字 ID。如果设为 false,而用户配置启用了 useNumberId,则会抛出错误。
supportsJSON
数据库是否支持 JSON。如果不支持,我们将使用字符串保存 JSON 数据,并在取出时安全地将字符串解析回 JSON 对象。
supportsDates
数据库是否支持日期。如果不支持,我们将把日期保存为字符串(ISO 字符串),取出时安全地解析回 Date 对象。
supportsBooleans
数据库是否支持布尔值。如果不支持,将使用 0 或 1 保存布尔值,取出时安全解析回布尔值。
usePlural
模式中的表名是否为复数形式。通常由用户定义,并通过你的自定义适配器选项传递。如果你不打算允许用户自定义表名,可以忽略此选项或设为 false。
const adapter = myAdapter({
// 该值会传递至 createAdapterFactory 的 `config` 对象中的 `usePlural`
usePlural: true,
});transaction
适配器是否支持事务。如果为 false,操作顺序执行;否则需提供一个函数,用于执行带 TransactionAdapter 的回调。
如果你的数据库不支持事务,错误处理和回滚不会那么健壮。建议使用支持事务的数据库以保证数据完整性。
debugLogs
启用适配器的调试日志。你可以传入布尔值,或一个包含 create、update、updateMany、findOne、findMany、delete、deleteMany、count 等键的对象。任何键值为 true 的方法会启用调试日志。
// 为所有方法启用调试日志。
const adapter = myAdapter({
debugLogs: true,
});// 仅为 `create` 和 `update` 方法启用调试日志。
const adapter = myAdapter({
debugLogs: {
create: true,
update: true,
},
});disableIdGeneration
是否禁用 ID 生成。如果设为 true,则忽略用户的 generateId 选项。
customIdGenerator
如果你的数据库只支持特定的自定义 ID 生成,可以使用此选项生成自己的 ID。
mapKeysTransformInput
如果数据库在某些情况下使用不同的键名,可以用此选项映射键名。适用于数据库期望某些键名不同的情况。
例如,MongoDB 使用 _id,而 Better-Auth 使用 id。
返回对象中的每个键表示待替换的旧键,值为新键。
可以是部分对象,仅转换部分键。
mapKeysTransformInput: {
id: "_id", // 我们希望将 `id` 替换为 `_id` 以保存进 MongoDB
},mapKeysTransformOutput
如果数据库在某些情况下使用不同的键名,可以用此选项映射键名。适用于数据库使用不同键名的场景。
例如,MongoDB 使用 _id,而 Better-Auth 使用 id。
返回对象中的每个键表示待替换的旧键,值为新键。
可以是部分对象,仅转换部分键。
mapKeysTransformOutput: {
_id: "id", // 我们希望将 `_id`(来自 MongoDB)替换为 `id`(供 Better-Auth 使用)
},customTransformInput
如果需要在数据保存到数据库前转换输入数据,可以使用此选项。
如果你启用了 supportsJSON、supportsDates 或 supportsBooleans,这些转换会在调用你的 customTransformInput 函数之前执行。
customTransformInput 函数接受以下参数:
data:需转换的数据。field:当前转换的字段。fieldAttributes:该字段的属性。action:调用的适配器动作(create或update)。model:当前模型。schema:当前模式。options:Better Auth 选项。
customTransformInput 函数在给定动作的数据对象的每个键上运行。
customTransformInput: ({ field, data }) => {
if (field === "id") {
return "123"; // 强制 ID 为 "123"
}
return data;
};customTransformOutput
如果需要在数据返回给用户前转换输出数据,可以使用此选项。
它和 customTransformInput 类似,但在数据从数据库取出后执行。
customTransformOutput: ({ field, data }) => {
if (field === "name") {
return "Bob"; // 强制姓名为 "Bob"
}
return data;
};const some_data = await adapter.create({
model: "user",
data: {
name: "John",
},
});
// 姓名将会是 "Bob"
console.log(some_data.name);disableTransformInput
是否禁用输入转换。只有在你明确知道自己在做什么,并手动处理所有转换时才应禁用。
禁用输入转换可能导致重要的适配器功能失效,例如 ID 生成、布尔/日期/JSON 转换和键映射。
disableTransformOutput
是否禁用输出转换。只有在你明确知道自己在做什么,并手动处理所有转换时才应禁用。
禁用输出转换可能导致重要的适配器功能失效,例如布尔/日期/JSON 解析和键映射。
disableTransformJoin
是否禁用连接转换。只有在你明确知道自己在做什么,并手动处理连接查询时才应禁用。
禁用连接转换可能导致连接功能失效。
supportsJoin
适配器是否支持原生连接查询。如果设置为 false(默认),Better-Auth 会通过多次查询合并结果来处理连接;如果设为 true,则适配器需支持原生连接(如 SQL JOIN 操作)。
// 适配器支持原生 SQL 连接查询
const adapter = myAdapter({
supportsJoin: true,
});