mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-10 04:19:32 +00:00
650 lines
18 KiB
Plaintext
650 lines
18 KiB
Plaintext
---
|
|
title: Database
|
|
description: Learn how to use a database with BetterAuth
|
|
---
|
|
|
|
Better Auth requires a database connection to store data. It comes with a query builder called <Link href="https://kysely.dev/"> Kysley </Link> to manage and query your database. The database will be used to store data such as users, sessions, and more. Plugins can also define their own database tables to store data.
|
|
|
|
You can pass a database connection to Better Auth by passing a supported database instance, a dialect instance or a kysely instance in the database options.
|
|
|
|
**Example: Sqlite**
|
|
```ts title="auth.ts"
|
|
import { betterAuth } from "better-auth"
|
|
import Database from "better-sqlite3"
|
|
|
|
export const auth = betterAuth({
|
|
database: new Database("database.sqlite")
|
|
})
|
|
```
|
|
|
|
**Example: Postgres**
|
|
```ts title="auth.ts"
|
|
import { betterAuth } from "better-auth"
|
|
import { Pool } from "pg"
|
|
|
|
export const auth = betterAuth({
|
|
database: new Pool({
|
|
connectionString: "postgres://user:password@localhost:5432/database"
|
|
})
|
|
})
|
|
```
|
|
|
|
**Example: Mysql**
|
|
```ts title="auth.ts"
|
|
import { betterAuth } from "better-auth"
|
|
import { createPool } from "mysql2/promise"
|
|
|
|
export const auth = betterAuth({
|
|
database: createPool({
|
|
host: "localhost",
|
|
user: "root",
|
|
password: "password",
|
|
database: "database"
|
|
})
|
|
})
|
|
```
|
|
|
|
**Example: Custom Dialect using libsql**
|
|
|
|
```ts title="auth.ts"
|
|
import { betterAuth } from "better-auth"
|
|
import { LibsqlDialect } from "@libsql/kysely-libsql";
|
|
|
|
export const auth = betterAuth({
|
|
database: {
|
|
dialect: new LibsqlDialect({
|
|
url: process.env.TURSO_DATABASE_URL || "",
|
|
authToken: process.env.TURSO_AUTH_TOKEN || "",
|
|
}),
|
|
type: "sqlite"
|
|
},
|
|
})
|
|
```
|
|
|
|
<Callout>
|
|
See <Link href="https://kysely.dev/docs/dialects" target="_blank"> Kysley Dialects </Link> for more dialects supported by kysley.
|
|
</Callout>
|
|
|
|
**Example: Custom Kysely Instance**
|
|
|
|
```ts title="auth.ts"
|
|
import { betterAuth } from "better-auth"
|
|
import { db } from "./db"
|
|
|
|
export const auth = betterAuth({
|
|
database: {
|
|
db: db,
|
|
type: "sqlite" // or "mysql", "postgres" or "mssql"
|
|
}
|
|
})
|
|
```
|
|
|
|
|
|
## Using Adapters
|
|
|
|
If your database is managed by an ORM like Prisma or Drizzle, you can use the corresponding adapter to connect to the database. Better Auth comes with built-in adapters for Prisma and Drizzle. You can pass the adapter to the `database` object in the auth options.
|
|
|
|
### Prisma Adapter
|
|
|
|
The Prisma adapter expects a prisma client instance and a provider key that specifies the database provider to use. The provider key can be `sqlite`, `postgres`, `mysql`, or any other supported by prisma.
|
|
|
|
```ts title="auth.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: "sqlite"
|
|
})
|
|
})
|
|
```
|
|
|
|
|
|
### Drizzle adapter
|
|
|
|
The Drizzle adapter expects a drizzle client instance and a provider key that specifies the database provider to use. The provider key can be `sqlite`, `pg` or `mysql`.
|
|
|
|
```ts title="auth.ts"
|
|
import { betterAuth } from "better-auth";
|
|
import { db } from "./drizzle";
|
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
|
|
export const auth = betterAuth({
|
|
database: drizzleAdapter(db, {
|
|
provider: "sqlite", // or "pg" or "mysql"
|
|
})
|
|
})
|
|
```
|
|
|
|
#### Mapping Schema
|
|
|
|
The Drizzle adapter expects the schema you define to match the table names. For example, if your Drizzle schema maps the `user` table to `users`, you need to manually pass the schema and map it to the user table.
|
|
|
|
```ts
|
|
import { betterAuth } from "better-auth";
|
|
import { db } from "./drizzle";
|
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
import { schema } from "./schema";
|
|
|
|
export const auth = betterAuth({
|
|
database: drizzleAdapter(db, {
|
|
provider: "sqlite", // or "pg" or "mysql"
|
|
schema: {
|
|
...schema,
|
|
user: schema.users,
|
|
},
|
|
//if all of them are just using plural form, you can just pass the option below
|
|
usePlural: true
|
|
})
|
|
})
|
|
```
|
|
|
|
### MongoDB Adapter
|
|
|
|
The MongoDB adapter expects a mongodb client instance and a database name. The adapter will create a new database with the provided name if it doesn't exist.
|
|
|
|
```ts title="auth.ts"
|
|
import { betterAuth } from "better-auth";
|
|
import { MongoClient } from "mongodb";
|
|
import { mongodbAdapter } from "better-auth/adapters/mongodb";
|
|
|
|
const client = new MongoClient("mongodb://localhost:27017");
|
|
|
|
const db = client.db()
|
|
|
|
export const auth = betterAuth({
|
|
database: mongodbAdapter(db)
|
|
})
|
|
```
|
|
|
|
#### Use Plural Form
|
|
|
|
If your schema uses plural form for table names, you can pass the `usePlural` option to the adapter.
|
|
|
|
```ts title="auth.ts"
|
|
import { betterAuth } from "better-auth";
|
|
import { MongoClient } from "mongodb";
|
|
import { mongoAdapter } from "better-auth/adapters/mongodb";
|
|
|
|
export const auth = betterAuth({
|
|
database: mongoAdapter(client, {
|
|
usePlural: true
|
|
})
|
|
})
|
|
```
|
|
|
|
## CLI
|
|
|
|
|
|
Better Auth comes with a CLI tool to manage database migrations and generate schema.
|
|
|
|
### Running Migrations
|
|
|
|
The cli checks your database and prompts you to add missing tables or update existing ones with new columns. This is only supported for the built-in Kysely adapter. For other adapters, you can use the `generate` command to create the schema and handle the migration through your ORM.
|
|
|
|
```bash
|
|
npx @better-auth/cli migrate
|
|
```
|
|
|
|
### Generating Schema
|
|
|
|
Better Auth also provides a `generate` command to generate the schema required by Better Auth. The `generate` command creates the schema required by Better Auth. If you're using a database adapter like Prisma or Drizzle, this command will generate the right schema for your ORM. If you're using the built-in Kysely adapter, it will generate an SQL file you can run directly on your database.
|
|
|
|
```bash
|
|
npx @better-auth/cli generate
|
|
```
|
|
|
|
See the [CLI](/docs/concepts/cli) documentation for more information on the CLI.
|
|
|
|
<Callout>
|
|
If you prefer adding tables manually, you can do that as well. The core schema required by Better Auth is described below and you can find additional schema required by plugins in the plugin documentation.
|
|
</Callout>
|
|
|
|
|
|
## Secondary Storage
|
|
|
|
Secondary storage in BetterAuth allows you to use key-value stores for managing session data, rate limiting counters, etc. This can be useful when you want to offload the storage of this intensive records to a high performance storage or even RAM.
|
|
|
|
### Implementation
|
|
|
|
To use secondary storage, implement the `SecondaryStorage` interface:
|
|
|
|
```typescript
|
|
interface SecondaryStorage {
|
|
get: (key: string) => Promise<string | null>
|
|
set: (
|
|
key: string,
|
|
value: string,
|
|
ttl?: number,
|
|
) => Promise<void>;
|
|
delete: (key: string) => Promise<void>;
|
|
}
|
|
```
|
|
|
|
Then, provide your implementation to the `betterAuth` function:
|
|
|
|
```typescript
|
|
betterAuth({
|
|
// ... other options
|
|
secondaryStorage: {
|
|
// Your implementation here
|
|
}
|
|
})
|
|
```
|
|
|
|
**Example: Redis Implementation**
|
|
|
|
Here's a basic example using Redis:
|
|
|
|
```typescript
|
|
import { createClient } from "redis";
|
|
import { betterAuth } from "better-auth";
|
|
|
|
const redis = createClient();
|
|
|
|
export const auth = betterAuth({
|
|
// ... other options
|
|
secondaryStorage: {
|
|
get: async (key) => await redis.get(key),
|
|
set: async (key, value, ttl) => {
|
|
if (ttl) await redis.set(key, JSON.stringify(value), { EX: ttl });
|
|
else await redis.set(key, JSON.stringify(value));
|
|
},
|
|
delete: async (key) => await redis.del(key),
|
|
}
|
|
});
|
|
```
|
|
|
|
This implementation allows BetterAuth to use Redis for storing session data and rate limiting counters.
|
|
|
|
## Core Schema
|
|
|
|
Better Auth requires the following tables to be present in the database. The types are in `typescript` format. You can use corresponding types in your database.
|
|
|
|
### User
|
|
|
|
Table Name: `user`
|
|
|
|
<DatabaseTable
|
|
fields={[
|
|
{
|
|
name: "id",
|
|
type: "string",
|
|
description: "Unique identifier for each user",
|
|
isPrimaryKey: true
|
|
},
|
|
{
|
|
name: "name",
|
|
type: "string",
|
|
description: "User's chosen display name"
|
|
},
|
|
{
|
|
name: "email",
|
|
type: "string",
|
|
description: "User's email address for communication and login"
|
|
},
|
|
{
|
|
name: "emailVerified",
|
|
type: "boolean",
|
|
description: "Whether the user's email is verified",
|
|
},
|
|
{
|
|
name: "image",
|
|
type: "string",
|
|
description: "User's image url",
|
|
isOptional: true
|
|
},
|
|
{
|
|
name: "createdAt",
|
|
type: "Date",
|
|
description: "Timestamp of when the user account was created"
|
|
},
|
|
{
|
|
name: "updatedAt",
|
|
type: "Date",
|
|
description: "Timestamp of the last update to the user's information"
|
|
},
|
|
]}
|
|
/>
|
|
|
|
### Session
|
|
|
|
Table Name: `session`
|
|
|
|
<DatabaseTable
|
|
fields={[
|
|
{
|
|
name: "id",
|
|
type: "string",
|
|
description: "Unique identifier for each session. This is used as the session token.",
|
|
isPrimaryKey: true
|
|
},
|
|
{
|
|
name: "userId",
|
|
type: "string",
|
|
description: "The id of the user",
|
|
isForeignKey: true
|
|
},
|
|
{
|
|
name: "expiresAt",
|
|
type: "Date",
|
|
description: "The time when the session expires"
|
|
},
|
|
{
|
|
name: "ipAddress",
|
|
type: "string",
|
|
description: "The IP address of the device",
|
|
isOptional: true
|
|
},
|
|
{
|
|
name: "userAgent",
|
|
type: "string",
|
|
description: "The user agent information of the device",
|
|
isOptional: true
|
|
},
|
|
]}
|
|
/>
|
|
|
|
### Account
|
|
|
|
Table Name: `account`
|
|
|
|
<DatabaseTable
|
|
fields={[
|
|
{
|
|
name: "id",
|
|
type: "string",
|
|
description: "Unique identifier for each account",
|
|
isPrimaryKey: true
|
|
},
|
|
{
|
|
name: "userId",
|
|
type: "string",
|
|
description: "The id of the user",
|
|
isForeignKey: true
|
|
},
|
|
{
|
|
name: "accountId",
|
|
type: "string",
|
|
description: "The id of the account as provided by the SSO or equal to userId for credential accounts",
|
|
},
|
|
{
|
|
name: "providerId",
|
|
type: "string",
|
|
description: "The id of the provider",
|
|
},
|
|
{
|
|
name: "accessToken",
|
|
type: "string",
|
|
description: "The access token of the account. Returned by the provider",
|
|
isOptional: true,
|
|
},
|
|
{
|
|
name: "refreshToken",
|
|
type: "string",
|
|
description: "The refresh token of the account. Returned by the provider",
|
|
isOptional: true,
|
|
},
|
|
{
|
|
name: "expiresAt",
|
|
type: "Date",
|
|
description: "The time when the access token expires",
|
|
isOptional: true,
|
|
},
|
|
{
|
|
name: "password",
|
|
type: "string",
|
|
description: "The password of the account. Mainly used for email and password authentication",
|
|
isOptional: true,
|
|
},
|
|
]}
|
|
/>
|
|
|
|
|
|
### Verification
|
|
|
|
Table Name: `verification`
|
|
|
|
<DatabaseTable
|
|
fields={[
|
|
{
|
|
name: "id",
|
|
type: "string",
|
|
description: "Unique identifier for each verification",
|
|
isPrimaryKey: true
|
|
},
|
|
{
|
|
name: "identifier",
|
|
type: "string",
|
|
description: "The identifier for the verification request"
|
|
},
|
|
{
|
|
name: "value",
|
|
type: "string",
|
|
description: "The value to be verified"
|
|
},
|
|
{
|
|
name: "expiresAt",
|
|
type: "Date",
|
|
description: "The time when the verification request expires"
|
|
},
|
|
{
|
|
name: "createdAt",
|
|
type: "Date",
|
|
description: "Timestamp of when the verification request was created"
|
|
}
|
|
]}
|
|
/>
|
|
|
|
|
|
## Custom Tables
|
|
|
|
Better Auth allows you to customize the table names and column names for the core schema. You can also extend the core schema by adding additional fields to the user and session tables.
|
|
|
|
### Custom Table Names
|
|
|
|
You can customize the table names and column names for the core schema by using the `modelName` and `fields` properties in your auth config:
|
|
|
|
```ts title="auth.ts"
|
|
export const auth = betterAuth({
|
|
user: {
|
|
modelName: "users",
|
|
fields: {
|
|
name: "full_name",
|
|
email: "email_address"
|
|
}
|
|
},
|
|
session: {
|
|
modelName: "user_sessions",
|
|
fields: {
|
|
userId: "user_id"
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
<Callout>
|
|
Type inference in your code will still use the original field names (e.g., `user.name`, not `user.full_name`).
|
|
</Callout>
|
|
|
|
To customize table names and column name for plugins, you can use the `schema` property in the plugin config:
|
|
|
|
```ts title="auth.ts"
|
|
import { betterAuth } from "better-auth";
|
|
|
|
export const auth = betterAuth({
|
|
plugins: {
|
|
twoFactor: {
|
|
schema: {
|
|
user: {
|
|
fields: {
|
|
twoFactorEnabled: "two_factor_enabled",
|
|
twoFactorSecret: "two_factor_secret"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
|
|
### Extending Core Schema
|
|
|
|
Better Auth provides a type-safe way to extend the `user` and `session` schemas. You can add custom fields to your auth config, and the CLI will automatically update the database schema. These additional fields will be properly inferred in functions like `useSession`, `signUp.email`, and other endpoints that work with user or session objects.
|
|
|
|
To add custom fields, use the `additionalFields` property in the `user` or `session` object of your auth config. The `additionalFields` object uses field names as keys, with each value being a `FieldAttributes` object containing:
|
|
|
|
- `type`: The data type of the field (e.g., "string", "number", "boolean").
|
|
- `required`: A boolean indicating if the field is mandatory.
|
|
- `defaultValue`: The default value for the field (note: this only applies in the JavaScript layer; in the database, the field will be optional).
|
|
- `input`: This determines whether a value can be provided when creating a new record (default: `true`). If there are additional fields, like `role`, that should not be provided by the user during signup, you can set this to `false`.
|
|
|
|
Here's an example of how to extend the user schema with additional fields:
|
|
|
|
```ts title="auth.ts"
|
|
import { betterAuth } from "better-auth";
|
|
|
|
export const auth = betterAuth({
|
|
user: {
|
|
additionalFields: {
|
|
role: {
|
|
type: "string",
|
|
required: false,
|
|
defaultValue: "user",
|
|
input: false // don't allow user to set role
|
|
},
|
|
lang: {
|
|
type: "string",
|
|
required: false,
|
|
defaultValue: "en",
|
|
}
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
Now you can access the additional fields in your application logic.
|
|
|
|
```ts
|
|
//on signup
|
|
const res = await auth.api.signUpEmail({
|
|
email: "test@example.com",
|
|
password: "password",
|
|
name: "John Doe",
|
|
lang: "fr"
|
|
})
|
|
|
|
//user object
|
|
res.user.role // > "admin"
|
|
res.user.lang // > "fr"
|
|
```
|
|
|
|
<Callout>
|
|
See the [Typescript](/docs/concepts/typescript#inferring-additional-fields-on-client) documentation for more information on how to infer additional fields on the client side.
|
|
</Callout>
|
|
|
|
### ID Generation
|
|
|
|
Better Auth uses `nanoId` by default to generate unique IDs for users, sessions, and other entities. If you want to customize how IDs are generated, you can pass your own function through the adapter or database configuration.
|
|
|
|
**Guidelines:**
|
|
|
|
- IDs need to be in string format.
|
|
- The ID should be unique for each entity type (like users, sessions, etc.).
|
|
- Since the session ID acts as the token, it should be both unique and sufficiently long for security.
|
|
|
|
**Example: Built In Kysley Adapter**
|
|
```ts title="auth.ts"
|
|
import { betterAuth } from "better-auth";
|
|
import { db } from "./db";
|
|
|
|
export const auth = betterAuth({
|
|
database: {
|
|
db: db,
|
|
generateId: () => {
|
|
// custom id generation logic
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
**Example: Prisma Adapter**
|
|
```ts title="auth.ts"
|
|
import { betterAuth } from "better-auth";
|
|
|
|
export const auth = betterAuth({
|
|
database: prismaAdapter(prisma, {
|
|
provider: "sqlite",
|
|
generateId: () => {
|
|
// custom id generation logic
|
|
}
|
|
})
|
|
})
|
|
```
|
|
|
|
You can also disable ID generation by setting the `generateId` option to `false` in your adapter configuration. This will assume your database will generate the ID automatically.
|
|
|
|
### Database Hooks
|
|
|
|
Database hooks allow you to define custom logic that can be executed during the lifecycle of core database operations in BetterAuth. You can create hooks for the following models: **user**, **session**, and **account**.
|
|
|
|
There are two types of hooks you can define:
|
|
|
|
#### 1. Before Hook
|
|
|
|
- **Purpose**: This hook is called before the respective entity (user, session, or account) is created or updated.
|
|
- **Behavior**: If the hook returns `false`, the operation will be aborted. And If it returns a data object, it'll replace the orginal payload.
|
|
|
|
#### 2. After Hook
|
|
|
|
- **Purpose**: This hook is called after the respective entity is created or updated.
|
|
- **Behavior**: You can perform additional actions or modifications after the entity has been successfully created or updated.
|
|
|
|
**Example Usage**
|
|
|
|
```typescript title="auth.ts"
|
|
import { betterAuth } from "better-auth";
|
|
|
|
export const auth = betterAuth({
|
|
databaseHooks: {
|
|
user: {
|
|
create: {
|
|
before: async (user) => {
|
|
// Modify the user object before it is created
|
|
return {
|
|
data: {
|
|
...user,
|
|
firstName: user.name.split(" ")[0],
|
|
lastName: user.name.split(" ")[1]
|
|
}
|
|
}
|
|
},
|
|
after: async (user) => {
|
|
//perform additional actions, like creating a stripe customer
|
|
},
|
|
},
|
|
},
|
|
}
|
|
})
|
|
```
|
|
|
|
## Plugins Schema
|
|
|
|
Plugins can define their own tables in the database to store additional data. They can also add columns to the core tables to store additional data. For example, the two factor authentication plugin adds the following columns to the `user` table:
|
|
|
|
- `twoFactorEnabled`: Whether two factor authentication is enabled for the user.
|
|
- `twoFactorSecret`: The secret key used to generate TOTP codes.
|
|
- `twoFactorBackupCodes`: Encrypted backup codes for account recovery.
|
|
|
|
To add new tables and columns to your database, you have two options:
|
|
|
|
`CLI`: Use the migrate or generate command. These commands will scan your database and guide you through adding any missing tables or columns.
|
|
`Manual Method`: Follow the instructions in the plugin documentation to manually add tables and columns.
|
|
|
|
Both methods ensure your database schema stays up-to-date with your plugins' requirements.
|
|
|