1---
2title: Aurora Data API
3description: Deploy a Better Auth Lambda with Aurora PostgreSQL over HTTPS and automatic IAM bindings.
4---
5
6This example creates Aurora PostgreSQL and serves Better Auth through a Lambda Function URL. Aurora needs a VPC, but Lambda reaches the Data API over HTTPS without joining that VPC.
7
8## Install
9
10```sh
11bun add @alchemy.run/better-auth better-auth@^1.7.5 kysely @distilled.cloud/aws
12```
13
14Configure AWS through an [Alchemy profile](/environments/profiles) in a region supporting Aurora PostgreSQL's Data API. No `pg` driver or TCP connection pool is required.
15
16## Define the database and network
17
18```typescript title="src/database.ts"
19import * as AWS from "alchemy/AWS";
20import * as Effect from "effect/Effect";
21
22export const AuthDb = Effect.gen(function* () {
23 const network = yield* AWS.EC2.Network("AuthNetwork", {
24 cidrBlock: "10.42.0.0/16",
25 availabilityZones: 2,
26 });
27 const securityGroup = yield* AWS.EC2.SecurityGroup("AuthSecurityGroup", {
28 vpcId: network.vpcId,
29 description: "Aurora auth database",
30 });
31 return yield* AWS.RDS.Aurora("AuthDb", {
32 databaseName: "auth",
33 subnetIds: network.privateSubnetIds,
34 securityGroupIds: [securityGroup.groupId],
35 dataApi: true,
36 });
37});
38```
39
40The Aurora composite creates the cluster, writer, and database-credentials secret. The security group needs no inbound Postgres rule for Data API access.
41
42## Define the auth Lambda
43
44```typescript title="src/function.ts"
45import { BetterAuth } from "@alchemy.run/better-auth";
46import { AuroraDataApi } from "@alchemy.run/better-auth/AuroraDataApi";
47import * as AWS from "alchemy/AWS";
48import * as Duration from "effect/Duration";
49import * as Effect from "effect/Effect";
50import { AuthDb } from "./database.ts";
51
52export default class AuthApi extends AWS.Lambda.Function<AuthApi>()(
53 "AuthApi",
54 {
55 main: import.meta.url,
56 functionUrl: true,
57 memorySize: 512,
58 timeout: Duration.seconds(30),
59 },
60 Effect.gen(function* () {
61 const auth = yield* BetterAuth({
62 basePath: "/api/auth",
63 emailAndPassword: { enabled: true },
64 });
65 return { fetch: auth.fetch };
66 }).pipe(Effect.provide(AuroraDataApi(AuthDb, { database: "auth" }))),
67) {}
68```
69
70The database layer binds cluster and secret identifiers and grants the function Data API and Secrets Manager permissions. The public Function URL serves auth endpoints under `/api/auth`.
71
72## Define the stack
73
74```typescript title="alchemy.run.ts"
75import * as Alchemy from "alchemy";
76import * as AWS from "alchemy/AWS";
77import * as Effect from "effect/Effect";
78import AuthApi from "./src/function.ts";
79
80export default Alchemy.Stack(
81 "BetterAuthAurora",
82 {
83 providers: AWS.providers(),
84 state: Alchemy.localState(),
85 },
86 Effect.gen(function* () {
87 const api = yield* AuthApi;
88 return { url: api.functionUrl };
89 }),
90);
91```
92
93The auth signing secret is generated and bound automatically, separately from Aurora's database-credentials secret. Preserve and protect local `.alchemy` state across deployments.
94
95## Deploy and migrate
96
97```sh
98bunx alchemy deploy
99```
100
101Automatic migrations wait for the writer and use the Data API with deploy-time AWS credentials. Those credentials need database and secret access too; the function's IAM bindings do not grant permissions to the deployer.
102
103Set `AUTH_ORIGIN` to the printed Function URL and verify the endpoint:
104
105```sh
106curl -i "$AUTH_ORIGIN/api/auth/get-session"
107```
108
109## Cluster and migration options
110
111For a bare `AWS.RDS.DBCluster` resource, provide an `AWS.SecretsManager.Secret` resource through the layer's `secret` option. Passing the Aurora composite, as above, also supplies the writer dependency needed to order migrations.
112
113The layer retries transient errors while a Serverless v2 cluster resumes, with bounded retries at deployment and runtime. SQL runs within each invocation without retaining a TCP connection across invocations.
114
115The layer's `migrate: false` option disables automatic migration support; Better Auth's own `migrate: false` also opts out of its migration Action. Automatic migrations are additive and do not move records between databases.
116
117See [configuration](/better-auth/guides/configuration), [migrations](/better-auth/guides/migrations), the [upgrade guide](/better-auth/upgrades/from-1-6-to-1-7), and the [`AuroraDataApi` reference](/providers/betterauth/auroradataapi).