1---
2title: "Part 1: Create an Auth service"
3description: Install Better Auth, provide a D1 database layer, and create an Effect service.
4sidebar:
5 order: 1
6---
7
8Build one application with email/password authentication, an Effect HTTP API, and GitHub sign-in. This tutorial uses Bun and Cloudflare D1; [other databases](/better-auth/databases) use the same Auth service.
9
10Use your existing Alchemy project with [Bun](https://bun.sh) and a [configured Cloudflare profile](/cloudflare/setup). The examples use Effect 4 and Better Auth 1.7.5.
11
12## Install the module
13
14```sh
15bun add @alchemy.run/better-auth better-auth@1.7.5 kysely
16```
17
18Use matching Alchemy and Better Auth integration releases. D1 uses Kysely internally; application code does not need to construct a Kysely client.
19
20## Declare the database
21
22```typescript
23// src/database.ts
24import * as Cloudflare from "alchemy/Cloudflare";
25
26export const AuthDb = Cloudflare.D1.Database("AuthDb");
27```
28
29Alchemy creates the database when you deploy. During local development, D1 runs in the local simulator.
30
31## Choose the authentication method
32
33```typescript
34// src/auth.ts
35import { BetterAuth } from "@alchemy.run/better-auth";
36
37const makeAuth = BetterAuth({
38 basePath: "/api/auth",
39 emailAndPassword: { enabled: true },
40});
41```
42
43Alchemy provisions a stable signing secret and applies D1 schema migrations. You do not need to create auth tables or supply a signing secret.
44
45## Expose an Effect service
46
47```diff lang="typescript"
48 // src/auth.ts
49 import { BetterAuth } from "@alchemy.run/better-auth";
50+import * as Context from "effect/Context";
51+import * as Effect from "effect/Effect";
52+import * as Layer from "effect/Layer";
53
54 const makeAuth = BetterAuth({
55 basePath: "/api/auth",
56 emailAndPassword: { enabled: true },
57 });
58+
59+export class Auth extends Context.Service<
60+ Auth,
61+ Effect.Success<typeof makeAuth>
62+>()("app/Auth") {
63+ static readonly layer = Layer.effect(Auth, makeAuth);
64+}
65```
66
67Handlers and middleware can now request the same typed service with `yield* Auth`.
68
69## Create the Worker
70
71```typescript
72// src/worker.ts
73import * as Cloudflare from "alchemy/Cloudflare";
74import * as Effect from "effect/Effect";
75import * as HttpServerResponse from "effect/http/HttpServerResponse";
76
77export default class AuthApi extends Cloudflare.Worker<AuthApi>()(
78 "AuthApi",
79 {
80 main: import.meta.url,
81 compatibility: { flags: ["nodejs_compat"] },
82 },
83 Effect.gen(function* () {
84 return { fetch: Effect.succeed(HttpServerResponse.text("Ready")) };
85 }),
86) {}
87```
88
89Alchemy selects an available local port. Use the URL it prints rather than assuming a particular port.
90
91## Provide the database layer
92
93```diff lang="typescript"
94 // src/worker.ts
95+import { CloudflareD1 } from "@alchemy.run/better-auth/CloudflareD1";
96+import * as Layer from "effect/Layer";
97+import { Auth } from "./auth.ts";
98+import { AuthDb } from "./database.ts";
99
100 Effect.gen(function* () {
101+ yield* Auth;
102 return { fetch: Effect.succeed(HttpServerResponse.text("Ready")) };
103- }),
104+ }).pipe(
105+ Effect.provide(Auth.layer.pipe(Layer.provide(CloudflareD1(AuthDb)))),
106+ ),
107```
108
109The host supplies the database; the Auth service stays independent of Cloudflare. This also registers the database binding and migration action.
110
111## Create the stack
112
113```typescript
114// alchemy.run.ts
115import * as Alchemy from "alchemy";
116import * as Cloudflare from "alchemy/Cloudflare";
117import * as Effect from "effect/Effect";
118import AuthApi from "./src/worker.ts";
119
120export default Alchemy.Stack(
121 "BetterAuthTutorial",
122 { providers: Cloudflare.providers(), state: Alchemy.localState() },
123 Effect.gen(function* () {
124 const api = yield* AuthApi;
125 return { url: api.url };
126 }),
127);
128```
129
130Local state keeps this walkthrough self-contained. Preserve its state directory so future deployments retain the signing secret and resource identities.
131
132## Start development
133
134```sh
135bunx alchemy dev --stage tutorial
136```
137
138In another terminal, paste the printed URL after running `read`, then press Enter:
139
140```sh
141read -r DEV_URL
142export DEV_URL
143curl "$DEV_URL"
144```
145
146Keep this shell for the following API checks. Update `DEV_URL` if a later restart prints a different address.
147
148```text
149Ready
150```
151
152Continue to [Part 2: Mount the HTTP API](/better-auth/tutorial/part-2).