MySQL
This example connects a Better Auth Lambda to an existing MySQL database over TCP with TLS. The database, credentials, and network access must already exist.
Install
Section titled “Install”bun add @alchemy.run/better-auth better-auth@^1.7.5 kysely mysql2Configure AWS through an Alchemy profile and set DATABASE_URL in the deploying environment. Both the deployer and Lambda must be able to reach that URL.
Define the database layer
Section titled “Define the database layer”import { MySQL } from "@alchemy.run/better-auth/MySQL";import * as Config from "effect/Config";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";
export const AuthDatabase = Layer.unwrap( Effect.gen(function* () { const url = yield* Config.Redacted("DATABASE_URL"); return MySQL(url, { pool: { ssl: { rejectUnauthorized: true } }, }); }),);The URL is bound during host construction; the mysql2 pool opens per invocation and closes when it settles. The TLS configuration suits hosted services such as PlanetScale; provide your database’s CA when it is not covered by the system trust store.
Define the auth Lambda
Section titled “Define the auth Lambda”import { BetterAuth } from "@alchemy.run/better-auth";import * as AWS from "alchemy/AWS";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import { AuthDatabase } from "./auth-database.ts";
export default class AuthApi extends AWS.Lambda.Function<AuthApi>()( "AuthApi", { main: import.meta.url, functionUrl: true, memorySize: 512, timeout: Duration.seconds(30), build: { install: ["mysql2"] }, }, Effect.gen(function* () { const auth = yield* BetterAuth({ basePath: "/api/auth", emailAndPassword: { enabled: true }, }); return { fetch: auth.fetch }; }).pipe(Effect.provide(AuthDatabase)),) {}The function exposes Better Auth at /api/auth through a public Function URL. The driver is installed in the deployment artifact, and the signing secret is generated and bound automatically.
Define the stack
Section titled “Define the stack”import * as Alchemy from "alchemy";import * as AWS from "alchemy/AWS";import * as Effect from "effect/Effect";import AuthApi from "./src/function.ts";
export default Alchemy.Stack( "BetterAuthMySQL", { providers: AWS.providers(), state: Alchemy.localState(), }, Effect.gen(function* () { const api = yield* AuthApi; return { url: api.functionUrl }; }),);Preserve and protect the local .alchemy state directory across deployments. The example assumes network access without VPC attachment; a private database also needs Lambda VPC and security-group configuration.
Deploy and migrate
Section titled “Deploy and migrate”bunx alchemy deployAutomatic migrations use the resolved URL and the same pool options before updating the function. The deployer needs schema-changing permissions; migrations create auth tables inside the existing database.
Set AUTH_ORIGIN to the printed Function URL and check the endpoint:
curl -i "$AUTH_ORIGIN/api/auth/get-session"Separate migration permissions
Section titled “Separate migration permissions”Set DATABASE_MIGRATION_URL alongside the runtime URL and replace src/auth-database.ts with this file:
import { MySQL } from "@alchemy.run/better-auth/MySQL";import * as Config from "effect/Config";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";
export const AuthDatabase = Layer.unwrap( Effect.gen(function* () { const url = yield* Config.Redacted("DATABASE_URL"); const migrationUrl = yield* Config.Redacted("DATABASE_MIGRATION_URL"); return MySQL(url, { migrate: migrationUrl, pool: { ssl: { rejectUnauthorized: true } }, }); }),);Configuration read during host construction is bound into its environment, including this migration URL. Use a deploy-only resource Output instead when the runtime must not receive privileged migration credentials.
Other hosts and existing schemas
Section titled “Other hosts and existing schemas”For Workers, prefer Hyperdrive with a MySQL origin and nodejs_compat. Changing adapters does not transfer users or sessions between databases.
Review MySQL index and string-length constraints in the 1.6 to 1.7 upgrade guide before upgrading a populated database. See configuration, migrations, and the MySQL reference.