1---
2title: Neon
3description: Deploy a Better Auth Worker with a Neon project and the serverless database driver.
4---
5
6This example provisions Neon and serves Better Auth from a Cloudflare Worker. The serverless driver uses WebSockets, so it needs neither Hyperdrive nor `pg`.
7
8## Install
9
10```sh
11bun add @alchemy.run/better-auth better-auth@^1.7.5 kysely @neondatabase/serverless
12```
13
14Configure Cloudflare and Neon through [Alchemy profiles](/environments/profiles), or supply `NEON_API_KEY` for Neon. You provide account credentials; the project resource creates the database, not the API key.
15
16## Define the database layer
17
18```typescript title="src/auth-database.ts"
19import { Neon as NeonDatabase } from "@alchemy.run/better-auth/Neon";
20import * as Neon from "alchemy/Neon";
21import * as Effect from "effect/Effect";
22import * as Layer from "effect/Layer";
23
24const AuthDb = Neon.Project("AuthDb");
25
26export const AuthDatabase = Layer.unwrap(
27 Effect.map(AuthDb, (db) => NeonDatabase(db.connectionUri)),
28);
29```
30
31The project's `connectionUri` Output is bound into the host environment and also supplies deploy-time migrations. The driver acquires one pool per request and closes it when that request settles.
32
33## Define the auth Worker
34
35```typescript title="src/worker.ts"
36import { BetterAuth } from "@alchemy.run/better-auth";
37import * as Cloudflare from "alchemy/Cloudflare";
38import * as Effect from "effect/Effect";
39import { AuthDatabase } from "./auth-database.ts";
40
41export default class AuthApi extends Cloudflare.Worker<AuthApi>()(
42 "AuthApi",
43 {
44 main: import.meta.url,
45 compatibility: { flags: ["nodejs_compat"] },
46 },
47 Effect.gen(function* () {
48 const auth = yield* BetterAuth({
49 basePath: "/api/auth",
50 emailAndPassword: { enabled: true },
51 });
52 return { fetch: auth.fetch };
53 }).pipe(Effect.provide(AuthDatabase)),
54) {}
55```
56
57This is an auth-only endpoint, with no frontend or application routes. Alchemy generates and binds the signing secret automatically.
58
59## Define the stack
60
61```typescript title="alchemy.run.ts"
62import * as Alchemy from "alchemy";
63import * as Cloudflare from "alchemy/Cloudflare";
64import * as Neon from "alchemy/Neon";
65import * as Effect from "effect/Effect";
66import * as Layer from "effect/Layer";
67import AuthApi from "./src/worker.ts";
68
69export default Alchemy.Stack(
70 "BetterAuthNeon",
71 {
72 providers: Layer.mergeAll(Cloudflare.providers(), Neon.providers()),
73 state: Alchemy.localState(),
74 },
75 Effect.gen(function* () {
76 const api = yield* AuthApi;
77 return { url: api.url.as<string>() };
78 }),
79);
80```
81
82Keep the local `.alchemy` state directory protected and persistent so subsequent deployments retain resource identities and the signing secret.
83
84## Deploy and verify
85
86```sh
87bunx alchemy deploy
88```
89
90The deployer must reach Neon with schema-changing permissions; migrations complete before the Worker is updated. Set `AUTH_ORIGIN` to the printed URL to check the endpoint:
91
92```sh
93curl -i "$AUTH_ORIGIN/api/auth/get-session"
94```
95
96## Connect an existing project
97
98To use an existing Neon URL, replace only `src/auth-database.ts` with this complete file:
99
100```typescript title="src/auth-database.ts"
101import { Neon } from "@alchemy.run/better-auth/Neon";
102import * as Config from "effect/Config";
103import * as Effect from "effect/Effect";
104import * as Layer from "effect/Layer";
105
106export const AuthDatabase = Layer.unwrap(
107 Effect.gen(function* () {
108 const url = yield* Config.Redacted("DATABASE_URL");
109 return Neon(url);
110 }),
111);
112```
113
114Set `DATABASE_URL` in the deploying environment; it is read and bound during host construction, not first discovered inside a request. The stack above still works, but this variant creates no Neon resource and does not need Neon API credentials.
115
116## Other connection layouts
117
118The same database layer supports Lambda, where the pool is scoped to each invocation. For stage isolation, a `Neon.Branch` connection URI can replace the project URI; register `Neon.providers()` whenever the stack provisions Neon resources.
119
120The layer's `migrate` option accepts a separate deploy-resolvable connection source, or `false` to disable its migration support. Configuration read during host construction is bound to the host, so use a resource Output for migration credentials that must remain deploy-only.
121
122Automatic migrations do not move existing auth records between databases. See [configuration](/better-auth/guides/configuration), [migrations](/better-auth/guides/migrations), the [upgrade guide](/better-auth/upgrades/from-1-6-to-1-7), and the [`Neon` reference](/providers/betterauth/neon).