Initial commit
This commit is contained in:
577
skills/better-auth/references/database-integration.md
Normal file
577
skills/better-auth/references/database-integration.md
Normal file
@@ -0,0 +1,577 @@
|
||||
# Database Integration
|
||||
|
||||
Better Auth supports multiple databases and ORMs for flexible data persistence.
|
||||
|
||||
## Supported Databases
|
||||
|
||||
- SQLite
|
||||
- PostgreSQL
|
||||
- MySQL/MariaDB
|
||||
- MongoDB
|
||||
- Any database with adapter support
|
||||
|
||||
## Direct Database Connection
|
||||
|
||||
### SQLite
|
||||
|
||||
```ts
|
||||
import { betterAuth } from "better-auth";
|
||||
import Database from "better-sqlite3";
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: new Database("./sqlite.db"),
|
||||
// or
|
||||
database: new Database(":memory:") // In-memory for testing
|
||||
});
|
||||
```
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
```ts
|
||||
import { betterAuth } from "better-auth";
|
||||
import { Pool } from "pg";
|
||||
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
// or explicit config
|
||||
host: "localhost",
|
||||
port: 5432,
|
||||
user: "postgres",
|
||||
password: "password",
|
||||
database: "myapp"
|
||||
});
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: pool
|
||||
});
|
||||
```
|
||||
|
||||
### MySQL
|
||||
|
||||
```ts
|
||||
import { betterAuth } from "better-auth";
|
||||
import { createPool } from "mysql2/promise";
|
||||
|
||||
const pool = createPool({
|
||||
host: "localhost",
|
||||
user: "root",
|
||||
password: "password",
|
||||
database: "myapp",
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10
|
||||
});
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: pool
|
||||
});
|
||||
```
|
||||
|
||||
## ORM Adapters
|
||||
|
||||
### Drizzle ORM
|
||||
|
||||
**Install:**
|
||||
```bash
|
||||
npm install drizzle-orm better-auth
|
||||
```
|
||||
|
||||
**Setup:**
|
||||
```ts
|
||||
import { betterAuth } from "better-auth";
|
||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import { Pool } from "pg";
|
||||
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL
|
||||
});
|
||||
|
||||
const db = drizzle(pool);
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: drizzleAdapter(db, {
|
||||
provider: "pg", // "pg" | "mysql" | "sqlite"
|
||||
schema: {
|
||||
// Optional: custom table names
|
||||
user: "users",
|
||||
session: "sessions",
|
||||
account: "accounts",
|
||||
verification: "verifications"
|
||||
}
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
**Generate Schema:**
|
||||
```bash
|
||||
npx @better-auth/cli generate --adapter drizzle
|
||||
```
|
||||
|
||||
### Prisma
|
||||
|
||||
**Install:**
|
||||
```bash
|
||||
npm install @prisma/client better-auth
|
||||
```
|
||||
|
||||
**Setup:**
|
||||
```ts
|
||||
import { betterAuth } from "better-auth";
|
||||
import { prismaAdapter } from "better-auth/adapters/prisma";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: prismaAdapter(prisma, {
|
||||
provider: "postgresql", // "postgresql" | "mysql" | "sqlite"
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
**Generate Schema:**
|
||||
```bash
|
||||
npx @better-auth/cli generate --adapter prisma
|
||||
```
|
||||
|
||||
**Apply to Prisma:**
|
||||
```bash
|
||||
# Add generated schema to schema.prisma
|
||||
npx prisma migrate dev --name init
|
||||
npx prisma generate
|
||||
```
|
||||
|
||||
### Kysely
|
||||
|
||||
**Install:**
|
||||
```bash
|
||||
npm install kysely better-auth
|
||||
```
|
||||
|
||||
**Setup:**
|
||||
```ts
|
||||
import { betterAuth } from "better-auth";
|
||||
import { kyselyAdapter } from "better-auth/adapters/kysely";
|
||||
import { Kysely, PostgresDialect } from "kysely";
|
||||
import { Pool } from "pg";
|
||||
|
||||
const db = new Kysely({
|
||||
dialect: new PostgresDialect({
|
||||
pool: new Pool({
|
||||
connectionString: process.env.DATABASE_URL
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: kyselyAdapter(db, {
|
||||
provider: "pg"
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
**Auto-migrate with Kysely:**
|
||||
```bash
|
||||
npx @better-auth/cli migrate --adapter kysely
|
||||
```
|
||||
|
||||
### MongoDB
|
||||
|
||||
**Install:**
|
||||
```bash
|
||||
npm install mongodb better-auth
|
||||
```
|
||||
|
||||
**Setup:**
|
||||
```ts
|
||||
import { betterAuth } from "better-auth";
|
||||
import { mongodbAdapter } from "better-auth/adapters/mongodb";
|
||||
import { MongoClient } from "mongodb";
|
||||
|
||||
const client = new MongoClient(process.env.MONGODB_URI!);
|
||||
await client.connect();
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: mongodbAdapter(client, {
|
||||
databaseName: "myapp"
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
**Generate Collections:**
|
||||
```bash
|
||||
npx @better-auth/cli generate --adapter mongodb
|
||||
```
|
||||
|
||||
## Core Database Schema
|
||||
|
||||
Better Auth requires these core tables/collections:
|
||||
|
||||
### User Table
|
||||
|
||||
```sql
|
||||
CREATE TABLE user (
|
||||
id TEXT PRIMARY KEY,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
emailVerified BOOLEAN DEFAULT FALSE,
|
||||
name TEXT,
|
||||
image TEXT,
|
||||
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
### Session Table
|
||||
|
||||
```sql
|
||||
CREATE TABLE session (
|
||||
id TEXT PRIMARY KEY,
|
||||
userId TEXT NOT NULL,
|
||||
expiresAt TIMESTAMP NOT NULL,
|
||||
ipAddress TEXT,
|
||||
userAgent TEXT,
|
||||
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (userId) REFERENCES user(id) ON DELETE CASCADE
|
||||
);
|
||||
```
|
||||
|
||||
### Account Table
|
||||
|
||||
```sql
|
||||
CREATE TABLE account (
|
||||
id TEXT PRIMARY KEY,
|
||||
userId TEXT NOT NULL,
|
||||
accountId TEXT NOT NULL,
|
||||
providerId TEXT NOT NULL,
|
||||
accessToken TEXT,
|
||||
refreshToken TEXT,
|
||||
expiresAt TIMESTAMP,
|
||||
scope TEXT,
|
||||
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (userId) REFERENCES user(id) ON DELETE CASCADE,
|
||||
UNIQUE(providerId, accountId)
|
||||
);
|
||||
```
|
||||
|
||||
### Verification Table
|
||||
|
||||
```sql
|
||||
CREATE TABLE verification (
|
||||
id TEXT PRIMARY KEY,
|
||||
identifier TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
expiresAt TIMESTAMP NOT NULL,
|
||||
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
## Schema Generation
|
||||
|
||||
### Using CLI
|
||||
|
||||
```bash
|
||||
# Generate schema files
|
||||
npx @better-auth/cli generate
|
||||
|
||||
# Specify adapter
|
||||
npx @better-auth/cli generate --adapter drizzle
|
||||
npx @better-auth/cli generate --adapter prisma
|
||||
|
||||
# Specify output
|
||||
npx @better-auth/cli generate --output ./db/schema.ts
|
||||
```
|
||||
|
||||
### Auto-migrate (Kysely only)
|
||||
|
||||
```bash
|
||||
npx @better-auth/cli migrate
|
||||
```
|
||||
|
||||
For other ORMs, apply generated schema manually.
|
||||
|
||||
## Custom Fields
|
||||
|
||||
Add custom fields to user table:
|
||||
|
||||
```ts
|
||||
export const auth = betterAuth({
|
||||
user: {
|
||||
additionalFields: {
|
||||
role: {
|
||||
type: "string",
|
||||
required: false,
|
||||
defaultValue: "user"
|
||||
},
|
||||
phoneNumber: {
|
||||
type: "string",
|
||||
required: false
|
||||
},
|
||||
subscriptionTier: {
|
||||
type: "string",
|
||||
required: false
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
After adding fields:
|
||||
```bash
|
||||
npx @better-auth/cli generate
|
||||
```
|
||||
|
||||
Update user with custom fields:
|
||||
```ts
|
||||
await authClient.updateUser({
|
||||
role: "admin",
|
||||
phoneNumber: "+1234567890"
|
||||
});
|
||||
```
|
||||
|
||||
## Plugin Schema Extensions
|
||||
|
||||
Plugins add their own tables/fields. Regenerate schema after adding plugins:
|
||||
|
||||
```bash
|
||||
npx @better-auth/cli generate
|
||||
```
|
||||
|
||||
### Two-Factor Plugin Tables
|
||||
|
||||
- `twoFactor`: Stores TOTP secrets, backup codes
|
||||
|
||||
### Passkey Plugin Tables
|
||||
|
||||
- `passkey`: Stores WebAuthn credentials
|
||||
|
||||
### Organization Plugin Tables
|
||||
|
||||
- `organization`: Organization data
|
||||
- `member`: Organization members
|
||||
- `invitation`: Pending invitations
|
||||
|
||||
## Migration Strategies
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
# Generate schema
|
||||
npx @better-auth/cli generate
|
||||
|
||||
# Apply migrations (Kysely)
|
||||
npx @better-auth/cli migrate
|
||||
|
||||
# Or manual (Prisma)
|
||||
npx prisma migrate dev
|
||||
|
||||
# Or manual (Drizzle)
|
||||
npx drizzle-kit push
|
||||
```
|
||||
|
||||
### Production
|
||||
|
||||
```bash
|
||||
# Review generated migration
|
||||
npx @better-auth/cli generate
|
||||
|
||||
# Test in staging
|
||||
# Apply to production with your ORM's migration tool
|
||||
|
||||
# Prisma
|
||||
npx prisma migrate deploy
|
||||
|
||||
# Drizzle
|
||||
npx drizzle-kit push
|
||||
|
||||
# Kysely
|
||||
npx @better-auth/cli migrate
|
||||
```
|
||||
|
||||
## Connection Pooling
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
```ts
|
||||
import { Pool } from "pg";
|
||||
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
max: 20, // Max connections
|
||||
idleTimeoutMillis: 30000,
|
||||
connectionTimeoutMillis: 2000,
|
||||
});
|
||||
```
|
||||
|
||||
### MySQL
|
||||
|
||||
```ts
|
||||
import { createPool } from "mysql2/promise";
|
||||
|
||||
const pool = createPool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0
|
||||
});
|
||||
```
|
||||
|
||||
## Database URLs
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
```env
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
|
||||
# Or with connection params
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/dbname?schema=public&connection_limit=10
|
||||
```
|
||||
|
||||
### MySQL
|
||||
|
||||
```env
|
||||
DATABASE_URL=mysql://user:password@localhost:3306/dbname
|
||||
```
|
||||
|
||||
### SQLite
|
||||
|
||||
```env
|
||||
DATABASE_URL=file:./dev.db
|
||||
# Or in-memory
|
||||
DATABASE_URL=:memory:
|
||||
```
|
||||
|
||||
### MongoDB
|
||||
|
||||
```env
|
||||
MONGODB_URI=mongodb://localhost:27017/dbname
|
||||
# Or Atlas
|
||||
MONGODB_URI=mongodb+srv://user:password@cluster.mongodb.net/dbname
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Indexes
|
||||
|
||||
Better Auth CLI auto-generates essential indexes:
|
||||
- `user.email` (unique)
|
||||
- `session.userId`
|
||||
- `account.userId`
|
||||
- `account.providerId, accountId` (unique)
|
||||
|
||||
Add custom indexes for performance:
|
||||
```sql
|
||||
CREATE INDEX idx_session_expires ON session(expiresAt);
|
||||
CREATE INDEX idx_user_created ON user(createdAt);
|
||||
```
|
||||
|
||||
### Query Optimization
|
||||
|
||||
```ts
|
||||
// Use connection pooling
|
||||
// Enable query caching where applicable
|
||||
// Monitor slow queries
|
||||
|
||||
export const auth = betterAuth({
|
||||
advanced: {
|
||||
defaultCookieAttributes: {
|
||||
sameSite: "lax",
|
||||
secure: true,
|
||||
httpOnly: true
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Backup Strategies
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
```bash
|
||||
# Backup
|
||||
pg_dump dbname > backup.sql
|
||||
|
||||
# Restore
|
||||
psql dbname < backup.sql
|
||||
```
|
||||
|
||||
### MySQL
|
||||
|
||||
```bash
|
||||
# Backup
|
||||
mysqldump -u root -p dbname > backup.sql
|
||||
|
||||
# Restore
|
||||
mysql -u root -p dbname < backup.sql
|
||||
```
|
||||
|
||||
### SQLite
|
||||
|
||||
```bash
|
||||
# Copy file
|
||||
cp dev.db dev.db.backup
|
||||
|
||||
# Or use backup command
|
||||
sqlite3 dev.db ".backup backup.db"
|
||||
```
|
||||
|
||||
### MongoDB
|
||||
|
||||
```bash
|
||||
# Backup
|
||||
mongodump --db=dbname --out=./backup
|
||||
|
||||
# Restore
|
||||
mongorestore --db=dbname ./backup/dbname
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Environment Variables**: Store credentials in env vars, never commit
|
||||
2. **Connection Pooling**: Use pools for PostgreSQL/MySQL in production
|
||||
3. **Migrations**: Use ORM migration tools, not raw SQL in production
|
||||
4. **Indexes**: Add indexes for frequently queried fields
|
||||
5. **Backups**: Automate daily backups in production
|
||||
6. **SSL**: Use SSL/TLS for database connections in production
|
||||
7. **Schema Sync**: Keep schema in sync across environments
|
||||
8. **Testing**: Use separate database for tests (in-memory SQLite ideal)
|
||||
9. **Monitoring**: Monitor query performance and connection pool usage
|
||||
10. **Cleanup**: Periodically clean expired sessions/verifications
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Errors
|
||||
|
||||
```ts
|
||||
// Add connection timeout
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
connectionTimeoutMillis: 5000
|
||||
});
|
||||
```
|
||||
|
||||
### Schema Mismatch
|
||||
|
||||
```bash
|
||||
# Regenerate schema
|
||||
npx @better-auth/cli generate
|
||||
|
||||
# Apply migrations
|
||||
# For Prisma: npx prisma migrate dev
|
||||
# For Drizzle: npx drizzle-kit push
|
||||
```
|
||||
|
||||
### Migration Failures
|
||||
|
||||
- Check database credentials
|
||||
- Verify database server is running
|
||||
- Check for schema conflicts
|
||||
- Review migration SQL manually
|
||||
|
||||
### Performance Issues
|
||||
|
||||
- Add indexes on foreign keys
|
||||
- Enable connection pooling
|
||||
- Monitor slow queries
|
||||
- Consider read replicas for heavy read workloads
|
||||
Reference in New Issue
Block a user