Database layers
BetterAuth() requires exactly one service: BetterAuth.Database.
Platform layers provide it. Pick the optimal layer for your
environment → database pair — HTTP/serverless transports where they
exist, TCP drivers as the fallback:
| Runtime | Database | Layer | Transport |
|---|---|---|---|
| Cloudflare Worker | D1 | CloudflareD1 |
native binding |
| Worker / Lambda | Neon | Neon |
serverless driver (WebSocket) |
| AWS Lambda | Aurora | AuroraDataApi |
RDS Data API (HTTPS, IAM) |
| Cloudflare Worker | any TCP Postgres/MySQL | CloudflareHyperdrive |
pooled TCP via Hyperdrive |
| anywhere with sockets | any Postgres | Postgres |
pg TCP |
| anywhere with sockets | any MySQL | MySQL |
mysql2 TCP |
| bun (dev/tests) | local file | SQLite |
bun:sqlite |
| tests | in-memory | Memory |
— |
| bring-your-own | your Drizzle db | Drizzle |
yours |
Layers are bindings
Section titled “Layers are bindings”Every layer routes runtime access through alchemy’s binding system — never hand-wired environment variables:
- Native bindings —
CloudflareD1binds the D1 database to the Worker;CloudflareHyperdrivebinds the Hyperdrive connection;AuroraDataApigrants the Lambdards-data:*+secretsmanager:GetSecretValueIAM through theAWS.RDSData.*bindings. - Environment Output bindings — connection-string layers (
Neon,Postgres,MySQL) accept resource Outputs (project.connectionUri,role.connectionUrl, …); the value is bound into the host environment at deploy and read back at runtime.
Provide the layer on the impl effect that yields BetterAuth:
Effect.gen(function* () { const auth = yield* BetterAuth({ emailAndPassword: { enabled: true } }); return { fetch: /* ... */ };}).pipe(Effect.provide(CloudflareD1(AuthDb)));When the layer needs a resource attribute (a connection string), build
it with Layer.unwrap so it still composes at module scope:
const AuthDatabase = Layer.unwrap( Effect.map(AuthDb, (db) => NeonDatabase(db.connectionUri)),);Secondary storage
Section titled “Secondary storage”Better Auth’s optional secondaryStorage (sessions, rate limiting,
OAuth state) is modeled as a second service, BetterAuth.SecondaryStorage,
resolved only when a layer provides it. There is deliberately no
Cloudflare KV implementation: Better Auth’s guidance is a Redis-style
store with strongly consistent reads and precise sub-minute TTLs, and
KV’s eventual consistency (plus its 60-second TTL floor) fights session
reads and rate-limit counters. A Durable Object-backed layer is the
right strongly-consistent Cloudflare option when one lands.
Choosing between Postgres options
Section titled “Choosing between Postgres options”- Neon — use the
Neonserverless driver everywhere; no Hyperdrive orpgneeded. - Aurora from Lambda — use
AuroraDataApi; the Lambda stays out of the VPC entirely. - Any other Postgres from a Worker — front it with
CloudflareHyperdrive. - Everything else —
Postgresoverpg. On Lambda, addbuild: { install: ["pg"] }so the dynamically-imported driver ships with an npm layout.