创建数据库适配器

了解如何为 Better-Auth 创建自定义数据库适配器

了解如何使用 createAdapterFactory 为 Better-Auth 创建自定义数据库适配器。

我们的 createAdapterFactory 函数设计得非常灵活,我们尽力让它易于理解和使用。
我们希望您可以专注于编写数据库逻辑,而无需担心适配器如何与 Better-Auth 配合工作。

从自定义模式配置、自定义 ID 生成、安全 JSON 解析、键映射、连接查询等,都由 createAdapterFactory 函数处理。
您只需提供数据库逻辑,createAdapterFactory 函数将处理剩余工作。

快速开始

准备工作

  1. 导入 createAdapterFactory
  2. 创建表示适配器配置选项的 CustomAdapterConfig 接口。
  3. 创建适配器!
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

然后使用 testAdaptercreateTestSuite 创建测试文件:

my-adapter.test.ts
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 中的 runAdapterTestrunNumberIdAdapterTest,该导出在 v1.5 中已移除。请改用 @better-auth/test-utils/adapter

配置

config 对象用于向 Better-Auth 提供关于适配器的信息。

我们强烈建议认真阅读以下各项配置选项,它们将帮助你正确配置适配器。

必需配置

adapterId

适配器的唯一标识符。

adapterName

适配器的名称。

可选配置

supportsNumericIds

数据库是否支持数字 ID。如果设为 false,而用户配置启用了 useNumberId,则会抛出错误。

supportsJSON

数据库是否支持 JSON。如果不支持,我们将使用字符串保存 JSON 数据,并在取出时安全地将字符串解析回 JSON 对象。

supportsDates

数据库是否支持日期。如果不支持,我们将把日期保存为字符串(ISO 字符串),取出时安全地解析回 Date 对象。

supportsBooleans

数据库是否支持布尔值。如果不支持,将使用 01 保存布尔值,取出时安全解析回布尔值。

usePlural

模式中的表名是否为复数形式。通常由用户定义,并通过你的自定义适配器选项传递。如果你不打算允许用户自定义表名,可以忽略此选项或设为 false

示例
const adapter = myAdapter({
  // 该值会传递至 createAdapterFactory 的 `config` 对象中的 `usePlural`
  usePlural: true,
});

transaction

适配器是否支持事务。如果为 false,操作顺序执行;否则需提供一个函数,用于执行带 TransactionAdapter 的回调。

如果你的数据库不支持事务,错误处理和回滚不会那么健壮。建议使用支持事务的数据库以保证数据完整性。

debugLogs

启用适配器的调试日志。你可以传入布尔值,或一个包含 createupdateupdateManyfindOnefindManydeletedeleteManycount 等键的对象。任何键值为 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

如果需要在数据保存到数据库前转换输入数据,可以使用此选项。

如果你启用了 supportsJSONsupportsDatessupportsBooleans,这些转换会在调用你的 customTransformInput 函数之前执行。

customTransformInput 函数接受以下参数:

  • data:需转换的数据。
  • field:当前转换的字段。
  • fieldAttributes:该字段的属性。
  • action:调用的适配器动作(createupdate)。
  • 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,
});