Cloudflare+Express+D1环境下Better Auth服务端方法使用及结构疑问
我在部署了Cloudflare D1绑定的Express.js/Node.js应用中使用Better Auth。由于D1数据库绑定(env.DB)仅在请求上下文内可用,我的认证设置通过Cloudflare Worker风格的fetch() handler实现,当前auth.ts代码如下:
import { betterAuth } from "better-auth"; export default { async fetch(request, env) { const auth = betterAuth({ database: env.DB, // D1 binding, auto-detected /// Rest of better auth config }); return auth.handler(request); }, } satisfies ExportedHandler<{ DB: D1Database }>;
我需要直接调用Better Auth的服务端方法(如signUpEmail()),相关代码如下:
import auth from "../../infrastructure/auth.ts"; export async function signUpWithEmail( pseudo: string, email: string, password: string ) { if (!pseudo || !email || !password) { throw new Error("Pseudo, email and password are required"); } try { const result = await auth.api.signUpEmail({ // 这里出现问题 body: { name: pseudo, email: email, password: password, display_pseudo: pseudo, }, }); } catch (error) { console.error(`Signup error: ${error}`); throw error; } }
但导入的auth对象仅包含fetch()方法,导致auth.api未定义/出现TypeScript报错。
约束条件
- 需通过
env.DB访问Better Auth(因Cloudflare D1); env.DB仅在请求上下文内可用;- 希望在服务中直接使用Better Auth的
signUpEmail、signInEmail等方法。
我的问题
- 当前
auth.ts结构是否适用于Cloudflare+Express+D1配置? - 在此架构下,暴露/使用Better Auth服务端方法的推荐方式是什么?
1. 当前auth.ts结构是否适用?
不适用。你现在的auth.ts导出的是Cloudflare Worker风格的handler对象,而非Better Auth实例本身。这种结构只能处理HTTP请求转发,无法直接暴露服务端API方法,且每次请求都会重新初始化Better Auth实例,既浪费资源也不符合Express应用的使用习惯。
2. 推荐的实现方式
由于env.DB仅能在请求上下文获取,需调整auth.ts结构,将Better Auth的初始化逻辑封装为可接收env参数的工厂函数,同时通过Express中间件注入实例,或提供上下文获取方法供服务层调用。
方案一:工厂函数+Express中间件注入
修改auth.ts,导出创建Better Auth实例的工厂函数,同时提供Express兼容的中间件:
import { betterAuth, type BetterAuth } from "better-auth"; import type { D1Database } from "@cloudflare/workers-types"; // 工厂函数:接收env返回Better Auth实例 export function createAuth(env: { DB: D1Database }): BetterAuth { return betterAuth({ database: env.DB, // 其他Better Auth配置 }); } // Express中间件:注入auth实例到请求对象 export function authMiddleware(env: { DB: D1Database }) { const auth = createAuth(env); return (req, res, next) => { // 挂载auth实例到req,供后续路由/服务使用 (req as any).auth = auth; next(); }; } // 保留Worker风格的fetch handler(兼容Worker部署) export default { async fetch(request, env) { const auth = createAuth(env); return auth.handler(request); }, } satisfies ExportedHandler<{ DB: D1Database }>;
在Express应用中配置中间件,并在服务层通过请求上下文获取实例:
// Express入口文件 import express from "express"; import { authMiddleware } from "./infrastructure/auth.ts"; import { signUpWithEmail } from "./services/authService.ts"; const app = express(); // 注入env(Cloudflare部署自动传入,本地开发可模拟) app.use(authMiddleware({ DB: process.env.DB as D1Database })); // 示例注册路由 app.post("/signup", async (req, res) => { try { const { pseudo, email, password } = req.body; const result = await signUpWithEmail(req.auth, pseudo, email, password); res.json(result); } catch (err) { res.status(400).json({ error: (err as Error).message }); } });
修改服务层函数,接收auth实例作为参数:
import type { BetterAuth } from "better-auth"; export async function signUpWithEmail( auth: BetterAuth, pseudo: string, email: string, password: string ) { if (!pseudo || !email || !password) { throw new Error("Pseudo, email and password are required"); } try { const result = await auth.api.signUpEmail({ body: { name: pseudo, email, password, display_pseudo: pseudo, }, }); return result; } catch (error) { console.error(`Signup error: ${error}`); throw error; } }
方案二:请求上下文容器(适用于复杂架构)
如果服务层不想直接依赖Express的req对象,可封装异步上下文容器,在请求开始时注入auth实例:
// context.ts import { AsyncLocalStorage } from "async_hooks"; import type { BetterAuth } from "better-auth"; const authContext = new AsyncLocalStorage<BetterAuth>(); export function setAuthContext(auth: BetterAuth) { return authContext.run(auth, () => {}); } export function getAuthContext(): BetterAuth { const auth = authContext.getStore(); if (!auth) throw new Error("Auth context not initialized"); return auth; }
修改authMiddleware注入上下文:
// auth.ts import { setAuthContext } from "./context.ts"; export function authMiddleware(env: { DB: D1Database }) { const auth = createAuth(env); return (req, res, next) => { setAuthContext(auth); next(); }; }
服务层直接通过上下文获取auth实例:
// authService.ts import { getAuthContext } from "../context.ts"; export async function signUpWithEmail( pseudo: string, email: string, password: string ) { const auth = getAuthContext(); // 后续逻辑同方案一 }
关键注意事项
- 禁止全局初始化Better Auth实例:
env.DB仅在请求上下文可用,全局初始化会导致无法获取数据库连接。 - 每个请求复用同一个实例:通过中间件在请求开始时创建实例,避免重复初始化的性能损耗。
- 保证TypeScript类型安全:显式声明
BetterAuth类型,规避类型报错。
内容的提问来源于stack exchange,提问作者Nazim M.

