Skip to content

Commit 3a2f035

Browse files
new commit
1 parent 6671885 commit 3a2f035

File tree

22 files changed

+7529
-18
lines changed

22 files changed

+7529
-18
lines changed
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# Better Auth Integration with Fastify and GraphQL
2+
3+
## Overview
4+
This document explains how **Better Auth** integrates with **Fastify** and **GraphQL** for authentication. It provides details on environment setup, database schema, middleware configuration, and how requests are handled.
5+
6+
---
7+
8+
## 1. Setting Up Environment Variables
9+
Make sure you have the required environment variables in your `.env` file. The `BETTER_AUTH_SECRET` must be set, and its value can be generated from [this link](https://better-auth.vercel.app/docs/installation).
10+
11+
### Required Environment Variables:
12+
```env
13+
BETTER_AUTH_SECRET=<your_generated_secret>
14+
API_POSTGRES_USER=<your_db_user>
15+
API_POSTGRES_PASSWORD=<your_db_password>
16+
API_POSTGRES_HOST=<your_db_host>
17+
API_POSTGRES_PORT=<your_db_port>
18+
API_POSTGRES_DATABASE=<your_db_name>
19+
API_CORS_ORIGIN = <your client URL>
20+
NODE_ENV=<Production or development or test>
21+
```
22+
23+
---
24+
25+
## 2. Connecting to PostgreSQL with Drizzle ORM
26+
27+
We use **Drizzle ORM** to connect to the PostgreSQL database. The connection URL is dynamically constructed from environment variables.
28+
29+
```typescript
30+
import dotenv from "dotenv";
31+
import { drizzle } from "drizzle-orm/postgres-js";
32+
import postgres from "postgres";
33+
34+
dotenv.config();
35+
36+
const DATABASE_URL = `postgres://${process.env.API_POSTGRES_USER}:${process.env.API_POSTGRES_PASSWORD}@${process.env.API_POSTGRES_HOST}:${process.env.API_POSTGRES_PORT}/${process.env.API_POSTGRES_DATABASE}`;
37+
38+
const client = postgres(DATABASE_URL, {
39+
prepare: false,
40+
// debug: (connection, query, params) => {
41+
// console.log("Running SQL Query:", query);
42+
// console.log("📌 Query Parameters:", params);
43+
// },
44+
});
45+
46+
// Connect Drizzle ORM
47+
export const db = drizzle(client);
48+
```
49+
50+
### Notes:
51+
- **Ensure `.env` variables are properly set** before running the application.
52+
- If you want to **debug SQL queries**, uncomment the `console.log` lines in the `debug` option.
53+
54+
---
55+
56+
## 3. Database Schema & Migrations
57+
The required database tables for **Better Auth** are already created, and migration files are present.
58+
59+
### Important Migrations:
60+
1. **Main Migration**: `20250122092015_sweet_scrambler.sql`
61+
2. **Better Auth Migration**: `20250303173255_cheerful_deadpool.sql`
62+
63+
### To Apply Migrations:
64+
Run the following command to ensure all database changes are applied:
65+
```sh
66+
npm run apply_drizzle_migrations
67+
```
68+
✅ If you have already migrated the **main migration file**, you only need to migrate `20250303173255_cheerful_deadpool.sql`. There is no need to manually create tables.
69+
70+
---
71+
72+
## 4. Authentication Middleware
73+
To integrate **Better Auth** with Fastify, we redirect all `/api/auth/*` requests to `auth.ts`, where authentication logic is handled.
74+
75+
### Updated `createServer` Code:
76+
```typescript
77+
fastify.all("/api/auth/*", async (req, reply) => {
78+
console.log(`✅ Route hit: ${req.method} ${req.url}`);
79+
80+
const headers: Record<string, string> = {};
81+
for (const [key, value] of Object.entries(req.headers)) {
82+
if (value) {
83+
headers[key] = Array.isArray(value) ? value.join(", ") : value;
84+
}
85+
}
86+
87+
const fetchRequest = new Request(
88+
`${req.protocol}://${req.hostname}${req.url}`,
89+
{
90+
method: req.method,
91+
headers,
92+
body:
93+
req.method !== "GET" && req.method !== "HEAD"
94+
? JSON.stringify(req.body)
95+
: undefined,
96+
}
97+
);
98+
99+
// Handle authentication
100+
const response = await auth.handler(fetchRequest);
101+
102+
// Send response back to Fastify
103+
reply.status(response.status);
104+
response.headers.forEach((value, key) => reply.header(key, value));
105+
reply.send(await response.text());
106+
});
107+
```
108+
109+
- This ensures that all authentication-related requests are handled by **Better Auth** before reaching GraphQL.
110+
- It converts Fastify requests into **Fetch API** requests, making them compatible with **Better Auth** handlers.
111+
112+
---
113+
114+
## 5. CORS Configuration
115+
To prevent **CORS errors**, we enable **CORS support** for both **GraphQL and Better Auth** using `fastify-cors`:
116+
117+
```typescript
118+
fastify.register(fastifyCors, {
119+
origin: "http://localhost:4321", // Allow only your frontend origin
120+
credentials: true, // Allow sending cookies and authentication headers
121+
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], // Allowed methods
122+
allowedHeaders: [
123+
"Content-Type",
124+
"Authorization",
125+
"apollo-require-preflight",
126+
],
127+
});
128+
```
129+
130+
- **Origin restriction:** Allows requests only from `http://localhost:4321`.
131+
- **Credentials enabled:** Ensures authentication headers and cookies are passed.
132+
- **Allowed methods:** `GET`, `POST`, `PUT`, `DELETE`, `OPTIONS`.
133+
134+
---
135+
136+
## 6. Better Auth Configuration (`auth.ts`)
137+
The authentication logic is defined inside `/src/lib/auth.ts`. Below is a summary of how **Better Auth** is configured:
138+
139+
```typescript
140+
export const auth = betterAuth({
141+
database: drizzleAdapter(db, {
142+
provider: "pg",
143+
schema: {
144+
user: usersTable,
145+
account: accountTable,
146+
session: sessionTable,
147+
verification: verificationTable,
148+
},
149+
}),
150+
advanced: {
151+
generateId: false,
152+
},
153+
user: {
154+
modelName: "user",
155+
fields: {
156+
email: "emailAddress",
157+
emailVerified: "isEmailAddressVerified",
158+
},
159+
},
160+
session: {
161+
expiresIn: 60 * 60 * 24 * 7, // 7 days
162+
updateAge: 60 * 60 * 24, // 1 day
163+
freshAge: 60 * 5, // 5 minutes
164+
cookieCache: {
165+
enabled: true,
166+
maxAge: 5 * 60,
167+
},
168+
},
169+
emailAndPassword: {
170+
enabled: true,
171+
},
172+
trustedOrigins: ["http://localhost:4321"],
173+
});
174+
```
175+
176+
### Key Features:
177+
- Uses **Drizzle ORM** as a database adapter.
178+
- Supports **email and password authentication**.
179+
- Implements **session management** with refresh-like cookie caching.
180+
- Allows **role-based access** and **trusted origins**.
181+
182+
---
183+
184+
## Conclusion
185+
You have successfully integrated **Better Auth** with Fastify and GraphQL. Ensure the **environment variables**, **migrations**, and **CORS settings** are properly configured. 🚀

docs/docs/docs/getting-started/environment-variables.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,22 @@ This environment variable is used to configure the host port to map with the con
563563

564564
- More information can be found at [this](https://docs.docker.com/engine/network/##published-ports) link.
565565

566+
### `API_CORS_ORIGIN`
566567

568+
This environment variable is used to specify the allowed origin(s) for Cross-Origin Resource Sharing (CORS). It defines which domains are permitted to interact with the API by sending requests from a different origin.
569+
570+
- **Usage Example:**
571+
```bash
572+
API_CORS_ORIGIN=http://example.com
573+
574+
### `BETTER_AUTH_SECRET`
575+
576+
This environment variable is used to define the secret key for signing and verifying authentication tokens in the Better Auth system. It is essential for ensuring the security and integrity of user sessions.
577+
578+
- **Usage Example:**
579+
```bash
580+
BETTER_AUTH_SECRET=your_super_secret_key_here
581+
567582
## docker compose
568583
569584
### COMPOSE_FILE
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
CREATE TABLE "account" (
2+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
3+
"account_id" text NOT NULL,
4+
"provider_id" text NOT NULL,
5+
"user_id" uuid NOT NULL,
6+
"access_token" text,
7+
"refresh_token" text,
8+
"id_token" text,
9+
"access_token_expires_at" timestamp,
10+
"refresh_token_expires_at" timestamp,
11+
"scope" text,
12+
"password" text,
13+
"created_at" timestamp NOT NULL,
14+
"updated_at" timestamp NOT NULL
15+
);
16+
--> statement-breakpoint
17+
CREATE TABLE "session" (
18+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
19+
"expires_at" timestamp NOT NULL,
20+
"token" text NOT NULL,
21+
"created_at" timestamp NOT NULL,
22+
"updated_at" timestamp NOT NULL,
23+
"ip_address" text,
24+
"user_agent" text,
25+
"user_id" uuid NOT NULL,
26+
CONSTRAINT "session_token_unique" UNIQUE("token")
27+
);
28+
--> statement-breakpoint
29+
CREATE TABLE "verification" (
30+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
31+
"identifier" text NOT NULL,
32+
"value" text NOT NULL,
33+
"expires_at" timestamp NOT NULL,
34+
"created_at" timestamp,
35+
"updated_at" timestamp
36+
);
37+
--> statement-breakpoint
38+
ALTER TABLE "users" ALTER COLUMN "is_email_address_verified" SET DEFAULT false;--> statement-breakpoint
39+
ALTER TABLE "users" ALTER COLUMN "is_email_address_verified" DROP NOT NULL;--> statement-breakpoint
40+
ALTER TABLE "users" ALTER COLUMN "password_hash" DROP NOT NULL;--> statement-breakpoint
41+
ALTER TABLE "users" ALTER COLUMN "role" SET DEFAULT 'regular';--> statement-breakpoint
42+
ALTER TABLE "users" ALTER COLUMN "role" DROP NOT NULL;--> statement-breakpoint
43+
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
44+
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;

0 commit comments

Comments
 (0)