创建数据库适配器

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

【Learn how to create a custom database adapter for Better-Auth using createAdapterFactory.】

我们的 createAdapterFactory 函数设计得非常灵活,我们也尽最大努力使其易于理解和使用。我们希望你能够专注于编写数据库逻辑,而不必担心适配器是如何与 Better-Auth 配合工作的。

【Our createAdapterFactory function is designed to be very flexible, and we've done our best to make it easy to understand and use. Our hope is to allow you to focus on writing database logic, and not have to worry about how the adapter is working with Better-Auth.】

从自定义模式配置、自定义 ID 生成、安全的 JSON 解析、键映射、关联等,所有这些都由 createAdapterFactory 函数处理。你只需要提供数据库逻辑,createAdapterFactory 函数会处理其余的部分。

【Anything from custom schema configurations, custom ID generation, safe JSON parsing, key mapping, joins, and more is handled by the createAdapterFactory function. All you need to do is provide the database logic, and the createAdapterFactory function will handle the rest.】

快速开始

【Quick Start】

准备事宜

【Get things ready】

  1. 导入 createAdapterFactory
  2. 创建表示你的适配器配置选项的 CustomAdapterConfig 接口。
  3. 创建适配器!
import { createAdapterFactory, type DBAdapterDebugLogOption } from "better-auth/adapters";

// Your custom adapter config options
interface CustomAdapterConfig {
  /**
   * Helps you debug issues with the adapter.
   */
  debugLogs?: DBAdapterDebugLogOption;
  /**
   * If the table names in the schema are plural.
   */
  usePlural?: boolean;
}

export const myAdapter = (config: CustomAdapterConfig = {}) =>
  createAdapterFactory({
    // ...
  });

配置适配器

【Configure the adapter】

config 对象主要用于向 Better-Auth 提供有关适配器的信息。我们尽量减少你在适配器函数中需要编写的代码量,这些 config 选项就是用来帮助我们做到这一点的。

【The config object is mostly used to provide information about the adapter to Better-Auth. We try to minimize the amount of code you need to write in your adapter functions, and these config options are used to help us do that.】

// ...
export const myAdapter = (config: CustomAdapterConfig = {}) =>
  createAdapterFactory({
    config: {
      adapterId: "custom-adapter", // A unique identifier for the adapter.
      adapterName: "Custom Adapter", // The name of the adapter.
      usePlural: config.usePlural ?? false, // Whether the table names in the schema are plural.
      debugLogs: config.debugLogs ?? false, // Whether to enable debug logs.
      supportsJSON: false, // Whether the database supports JSON. (Default: false)
      supportsDates: true, // Whether the database supports dates. (Default: true)
      supportsBooleans: true, // Whether the database supports booleans. (Default: true)
      supportsNumericIds: true, // Whether the database supports auto-incrementing numeric IDs. (Default: true)
    },
    // ...
  });

创建适配器

【Create the adapter】

adapter 函数是你编写与数据库交互的代码的地方。

【The adapter function is where you write the code that interacts with your database.】

// ...
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 的更多信息 here

适配器

【Adapter】

adapter 函数是你编写与数据库交互的代码的地方。

【The adapter function is where you write the code that interacts with your database.】

如果你还没有查看,请查看 配置部分 中的 options 对象,它对你的适配器可能很有用。

【If you haven't already, check out the options object in the config section, as it can be useful for your adapter.】

在我们进入适配器函数之前,让我们先了解一下可用的参数。

【Before we get into the adapter function, let's go over the parameters that are available to you.】

  • options:更好的认证选项。
  • schema:来自用户 Better Auth 实例的 schema。
  • debugLog:调试日志功能。
  • getFieldName:用于获取数据库中转换后的字段名称的函数。
  • getModelName:用于获取数据库转换后模型名称的函数。
  • getDefaultModelName:用于从模式中获取默认模型名称的函数。
  • getDefaultFieldName:用于从模式中获取默认字段名称的函数。
  • getFieldAttributes:用于获取特定模型和字段的字段属性的函数。
  • transformInput:在保存到数据库之前转换输入数据的函数。
  • transformOutput:用于在从数据库检索数据后转换输出数据的函数。
  • transformWhereClause:用于转换数据库查询中 where 子句的函数。
Example
adapter: ({
  options,
  schema,
  debugLog,
  getFieldName,
  getModelName,
  getDefaultModelName,
  getDefaultFieldName,
  getFieldAttributes,
  transformInput,
  transformOutput,
  transformWhereClause,
}) => {
  return {
    // ...
  };
};

适配器方法

【Adapter Methods】

  • 所有 model 值已经根据终端用户的模式配置转换为数据库中正确的模型名称。
    • 这也意味着,如果你需要访问给定模型的 schema 版本,你不能使用这个确切的 model 值,你需要使用选项中提供的 getDefaultModelName 函数将 model 转换为 schema 版本。
  • 我们将根据用户的 schema 配置自动填写你返回的任何缺失字段。
  • 任何包含 select 参数的方法,仅用于更高效地从数据库获取数据。你无需担心只返回 select 参数指定的内容,因为我们会为你处理这个问题。

create 方法

create method】

create 方法用于在数据库中创建一条新记录。

【The create method is used to create a new record in the database.】

可以将 forceAllowId 作为参数传递给 create 方法,这样就可以在 data 对象中提供 id。 我们在内部处理 forceAllowId,所以你不需要担心它。

参数:

【parameters:】

  • model:新数据将被插入的模型/表名。
  • data:要插入数据库的数据。
  • select:一个数组,用于指定从数据库中返回的字段。

确保返回插入到数据库中的数据。

Example
create: async ({ model, data, select }) => {
  // Example of inserting data into the database.
  return await db.insert(model).values(data);
};

update 方法

update method】

update 方法用于更新数据库中的记录。

【The update method is used to update a record in the database.】

参数:

【parameters:】

  • model:记录将被更新的模型/表名称。
  • where:用于更新记录的 where 子句。
  • update:用于更新记录的数据。

确保返回更新后的行中的数据。这包括任何未被更新的字段。

Example
update: async ({ model, where, update }) => {
  // Example of updating data in the database.
  return await db.update(model).set(update).where(where);
};

updateMany 方法

updateMany method】

updateMany 方法用于更新数据库中的多条记录。

【The updateMany method is used to update multiple records in the database.】

参数:

【parameters:】

  • model:将要更新记录的模型/表名称。
  • where:用于更新记录的 where 子句。
  • update:用于更新记录的数据。
确保返回已更新的记录数。
Example
updateMany: async ({ model, where, update }) => {
  // Example of updating multiple records in the database.
  return await db.update(model).set(update).where(where);
};

delete 方法

delete method】

delete 方法用于从数据库中删除一条记录。

【The delete method is used to delete a record from the database.】

参数:

【parameters:】

  • model:记录将被删除的模型/表名称。
  • where:用于根据条件删除记录的 where 子句。
Example
delete: async ({ model, where }) => {
  // Example of deleting a record from the database.
  await db.delete(model).where(where);
}

deleteMany 方法

deleteMany method】

deleteMany 方法用于从数据库中删除多条记录。

【The deleteMany method is used to delete multiple records from the database.】

参数:

【parameters:】

  • model:将要删除记录的模型/表名称。
  • where:用于删除记录的 where 子句。
确保返回已删除记录的数量。
Example
deleteMany: async ({ model, where }) => {
  // Example of deleting multiple records from the database.
  return await db.delete(model).where(where);
};

findOne 方法

findOne method】

findOne 方法用于在数据库中查找单条记录。

【The findOne method is used to find a single record in the database.】

参数:

【parameters:】

  • model:记录所在的模型/表名称。
  • where:用于查找记录的 where 条件。
  • select:要返回的 select 子句。
  • join:可选的连接配置,用于在一次查询中获取相关记录。
确保返回数据库中找到的数据。
Example
findOne: async ({ model, where, select, join }) => {
  // Example of finding a single record in the database.
  return await db.select().from(model).where(where).limit(1);
};

findMany 方法

findMany method】

findMany 方法用于在数据库中查找多条记录。

【The findMany method is used to find multiple records in the database.】

参数:

【parameters:】

  • model:记录所在的模型/表名称。
  • where:用于查找记录的 where 条件。
  • limit:要返回的记录数限制。
  • sortBy:用于按指定字段对记录进行排序的 sortBy 条款。
  • offset:要返回的记录偏移量。
  • join:可选的连接配置,用于在一次查询中获取相关记录。

确保返回从数据库中找到的数据数组。

Example
findMany: async ({ model, where, limit, sortBy, offset, join }) => {
  // Example of finding multiple records in the database.
  return await db
    .select()
    .from(model)
    .where(where)
    .limit(limit)
    .offset(offset)
    .orderBy(sortBy);
};

count 方法

count method】

count 方法用于统计数据库中的记录数量。

【The count method is used to count the number of records in the database.】

参数:

【parameters:】

  • model:记录将被计数的模型/表名。
  • where:用于按条件统计记录的 where 子句。
确保返回被统计的记录数量。
Example
count: async ({ model, where }) => {
  // Example of counting the number of records in the database.
  return await db.select().from(model).where(where).count();
};

options(可选)

options (optional)】

options 对象用于存放从自定义适配器选项获得的任何可能的配置。

【The options object is for any potential config that you got from your custom adapter options.】

Example
const myAdapter = (config: CustomAdapterConfig) =>
  createAdapterFactory({
    config: {
      // ...
    },
    adapter: ({ options }) => {
      return {
        options: config,
      };
    },
  });

createSchema(可选)

createSchema (optional)】

createSchema 方法允许 Better Auth CLI 为数据库 生成 一个模式。

【The createSchema method allows the Better Auth CLI to generate a schema for the database.】

参数:

【parameters:】

  • tables:来自用户 Better-Auth 实例的表;预计将生成到架构文件中。
  • file:用户可能在 generate 命令中传入的,作为预期模式文件输出路径的文件。
Example
createSchema: async ({ file, tables }) => {
  // ... Custom logic to create a schema for the database.
};

测试你的适配器

【Test your adapter】

我们提供了一个测试套件,你可以用它来测试你的适配器。它要求你使用 vitest

【We've provided a test suite that you can use to test your adapter. It requires you to use vitest.】

my-adapter.test.ts
import { expect, test, describe } from "vitest";
import { runAdapterTest } from "better-auth/adapters/test";
import { myAdapter } from "./my-adapter";

describe("My Adapter Tests", async () => {
  afterAll(async () => {
    // Run DB cleanup here...
  });
  const adapter = myAdapter({
    debugLogs: {
      // If your adapter config allows passing in debug logs, then pass this here.
      isRunningAdapterTests: true, // This is our super secret flag to let us know to only log debug logs if a test fails.
    },
  });

  runAdapterTest({
    getAdapter: async (betterAuthOptions = {}) => {
      return adapter(betterAuthOptions);
    },
  });
});

数字 ID 测试

【Numeric ID tests】

如果你的数据库支持数字 ID,那么你也应该运行此测试:

【If your database supports numeric IDs, then you should run this test as well:】

my-adapter.number-id.test.ts
import { expect, test, describe } from "vitest";
import { runNumberIdAdapterTest } from "better-auth/adapters/test";
import { myAdapter } from "./my-adapter";

describe("My Adapter Numeric ID Tests", async () => {
  afterAll(async () => {
    // Run DB cleanup here...
  });
  const adapter = myAdapter({
    debugLogs: {
      // If your adapter config allows passing in debug logs, then pass this here.
      isRunningAdapterTests: true, // This is our super secret flag to let us know to only log debug logs if a test fails.
    },
  });

  runNumberIdAdapterTest({
    getAdapter: async (betterAuthOptions = {}) => {
      return adapter(betterAuthOptions);
    },
  });
});

配置

【Config】

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

【The config object is used to provide information about the adapter to Better-Auth.】

我们强烈建议仔细查看并阅读下面提供的每个选项,这将帮助你了解如何正确配置你的适配器。

【We highly recommend going through and reading each provided option below, as it will help you understand how to properly configure your adapter.】

必需配置

【Required Config】

adapterId

适配器的唯一标识符。

【A unique identifier for the adapter.】

adapterName

适配器的名称。

【The name of the adapter.】

可选配置

【Optional Config】

supportsNumericIds

数据库是否支持数字 ID。如果此选项设置为 false 且用户配置启用了 useNumberId,则我们将抛出错误。

【Whether the database supports numeric IDs. If this is set to false and the user's config has enabled useNumberId, then we will throw an error.】

supportsJSON

数据库是否支持 JSON。如果数据库不支持 JSON,我们将使用 string 来保存 JSON 数据。在检索数据时,我们会安全地将 string 解析回 JSON 对象。

【Whether the database supports JSON. If the database doesn't support JSON, we will use a string to save the JSON data.And when we retrieve the data, we will safely parse the string back into a JSON object.】

supportsDates

数据库是否支持日期。如果数据库不支持日期,我们将使用 string 来保存日期。(ISO 字符串)当我们检索数据时,我们会安全地将 string 解析回 Date 对象。

【Whether the database supports dates. If the database doesn't support dates, we will use a string to save the date. (ISO string) When we retrieve the data, we will safely parse the string back into a Date object.】

supportsBooleans

数据库是否支持布尔值。如果数据库不支持布尔值,我们将使用 01 来保存布尔值。检索数据时,我们会将 01 安全地解析回布尔值。

【Whether the database supports booleans. If the database doesn't support booleans, we will use a 0 or 1 to save the boolean value. When we retrieve the data, we will safely parse the 0 or 1 back into a boolean value.】

usePlural

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

【Whether the table names in the schema are plural. This is often defined by the user, and passed down through your custom adapter options. If you do not intend to allow the user to customize the table names, you can ignore this option, or set this to false.】

Example
const adapter = myAdapter({
  // This value then gets passed into the `usePlural`
  // option in the createAdapterFactory `config` object.
  usePlural: true,
});

transaction

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

【Whether the adapter supports transactions. If false, operations run sequentially; otherwise provide a function that executes a callback with a TransactionAdapter.】

如果你的数据库不支持事务,错误处理和回滚将不会那么可靠。我们建议使用支持事务的数据库,以获得更好的数据完整性。

debugLogs

用于启用适配器的调试日志。你可以传入一个布尔值,或者一个包含以下键的对象:createupdateupdateManyfindOnefindManydeletedeleteManycount。 如果任何键为 true,该方法的调试日志将被启用。

【Used to enable debug logs for the adapter. You can pass in a boolean, or an object with the following keys: create, update, updateMany, findOne, findMany, delete, deleteMany, count. If any of the keys are true, the debug logs will be enabled for that method.】

Example
// Will log debug logs for all methods.
const adapter = myAdapter({
  debugLogs: true,
});
Example
// Will only log debug logs for the `create` and `update` methods.
const adapter = myAdapter({
  debugLogs: {
    create: true,
    update: true,
  },
});

disableIdGeneration

是否禁用 ID 生成。如果设置为 true,则会忽略用户的 generateId 选项。

【Whether to disable ID generation. If this is set to true, then the user's generateId option will be ignored.】

customIdGenerator

如果你的数据库只支持特定的自定义 ID 生成,那么你可以使用此选项来生成自己的 ID。

【If your database only supports a specific custom ID generation, then you can use this option to generate your own IDs.】

mapKeysTransformInput

如果你的数据库在特定情况下使用不同的键名,你可以使用此选项来映射这些键。这对于那些在特定情况下使用不同键名的数据库非常有用。例如,MongoDB 使用 _id,而在 Better-Auth 中我们使用 id

【If your database uses a different key name for a given situation, you can use this option to map the keys. This is useful for databases that expect a different key name for a given situation. For example, MongoDB uses _id while in Better-Auth we use id.】

返回对象中的每个键表示要替换的旧键。值表示新的键。

【Each key in the returned object represents the old key to replace. The value represents the new key.】

这可以是一个只转换某些键的部分对象。

【This can be a partial object that only transforms some keys.】

Example
mapKeysTransformInput: {
  id: "_id", // We want to replace `id` to `_id` to save into MongoDB
},

mapKeysTransformOutput

如果你的数据库在特定情况下使用不同的键名,你可以使用此选项来映射这些键。这对于在特定情况下使用不同键名的数据库非常有用。例如,MongoDB 使用 _id,而在 Better-Auth 中我们使用 id

【If your database uses a different key name for a given situation, you can use this option to map the keys. This is useful for databases that use a different key name for a given situation. For example, MongoDB uses _id while in Better-Auth we use id.】

返回对象中的每个键表示要替换的旧键。值表示新的键。

【Each key in the returned object represents the old key to replace. The value represents the new key.】

这可以是一个只转换某些键的部分对象。

【This can be a partial object that only transforms some keys.】

Example
mapKeysTransformOutput: {
  _id: "id", // We want to replace `_id` (from MongoDB) to `id` (for Better-Auth)
},

customTransformInput

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

【If you need to transform the input data before it is saved to the database, you can use this option to transform the data.】

如果你使用 supportsJSONsupportsDatessupportsBooleans,那么这些转换会在调用你的 customTransformInput 函数之前应用。

customTransformInput 函数接收以下参数:

  • data:要转换的数据。
  • field:正在被转换的字段。
  • fieldAttributes:正在转换的字段的字段属性。
  • action:从适配器调用的操作(createupdate)。
  • model:正在被转换的模型。
  • schema:正在被转换的模式。
  • options:更好的认证选项。

customTransformInput 函数会在给定操作的数据对象的每个键上运行。

【The customTransformInput function runs at every key in the data object of a given action.】

Example
customTransformInput: ({ field, data }) => {
  if (field === "id") {
    return "123"; // Force the ID to be "123"
  }

  return data;
};

customTransformOutput

如果你需要在将输出数据返回给用户之前对其进行转换,可以使用此选项来转换数据。customTransformOutput 函数用于转换输出数据。与 customTransformInput 函数类似,它会在给定操作的数据对象的每个键上运行,但它是在从数据库检索数据之后运行的。

【If you need to transform the output data before it is returned to the user, you can use this option to transform the data. The customTransformOutput function is used to transform the output data. Similar to the customTransformInput function, it runs at every key in the data object of a given action, but it runs after the data is retrieved from the database.】

Example
customTransformOutput: ({ field, data }) => {
  if (field === "name") {
    return "Bob"; // Force the name to be "Bob"
  }

  return data;
};
const some_data = await adapter.create({
  model: "user",
  data: {
    name: "John",
  },
});

// The name will be "Bob"
console.log(some_data.name);

disableTransformInput

是否禁用输入转换。只有在你清楚自己在做什么并且手动处理所有转换时才应该使用此选项。

【Whether to disable input transformation. This should only be used if you know what you're doing and are manually handling all transformations.】

禁用输入转换可能会破坏重要的适配器功能,例如 ID 生成、布尔值/日期/JSON 转换和键映射。

disableTransformOutput

是否禁用输出转换。只有在你清楚自己在做什么并且手动处理所有转换时才应该使用此选项。

【Whether to disable output transformation. This should only be used if you know what you're doing and are manually handling all transformations.】

禁用输出转换可能会破坏重要的适配器功能,例如布尔值/日期/JSON 解析和键映射。

disableTransformJoin

是否禁用连接转换。只有在你清楚自己在做什么并且手动处理连接时才应使用此选项。

【Whether to disable join transformation. This should only be used if you know what you're doing and are manually handling joins.】

禁用连接转换可能会导致连接功能失效。

supportsJoin

适配器是否支持原生连接。如果设置为 false(默认值),Better-Auth 将通过执行多次查询并组合结果来处理连接。如果设置为 true,则适配器需要原生处理连接(例如 SQL JOIN 操作)。

【Whether the adapter supports native joins. If set to false (the default), Better-Auth will handle joins by making multiple queries and combining the results. If set to true, the adapter is expected to handle joins natively (e.g., SQL JOIN operations).】

Example
// For adapters that support native SQL joins
const adapter = myAdapter({
  supportsJoin: true,
});