Adapters
Prisma Adapter
Use Prisma with Keyloom. Complete schema, setup, migrations, configuration, performance, and troubleshooting.
Prisma Adapter
Use Prisma to persist users, accounts, sessions, tokens, refresh tokens, and memberships. This page provides a complete schema and production setup.
Prerequisites
- Database available (PostgreSQL, MySQL, or SQLite)
- Prisma CLI installed (added automatically by
prisma init) - DATABASE_URL configured in env
Install
npm install @prisma/client
npx prisma initpnpm add @prisma/client
pnpm dlx prisma inityarn add @prisma/client
yarn dlx prisma initbun add @prisma/client
bunx prisma initPrisma schema (PostgreSQL)
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String? @unique
name String?
createdAt DateTime @default(now())
accounts Account[]
sessions Session[]
memberships Membership[]
refreshTokens RefreshToken[]
}
model Account {
id String @id @default(cuid())
provider String
providerAccountId String
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
accessToken String?
refreshToken String?
tokenType String?
scope String?
expiresAt Int?
createdAt DateTime @default(now())
@@unique([provider, providerAccountId])
@@index([userId])
}
model Session {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
expiresAt DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([expiresAt])
}
model VerificationToken {
id String @id @default(cuid())
identifier String
tokenHash String
expiresAt DateTime
createdAt DateTime @default(now())
@@index([identifier])
@@index([expiresAt])
}
model RefreshToken {
jti String @id
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
tokenHash String
expiresAt DateTime
parentJti String?
createdAt DateTime @default(now())
@@index([userId])
@@index([expiresAt])
}
model Membership {
userId String
orgId String
role String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([userId, orgId])
@@index([orgId])
}
model AuditEvent {
id String @id @default(cuid())
type String
userId String?
orgId String?
ip String?
userAgent String?
meta Json?
createdAt DateTime @default(now())
@@index([userId])
@@index([orgId])
@@index([createdAt])
}Environment
# PostgreSQL
DATABASE_URL=postgresql://user:password@localhost:5432/keyloom?schema=public
# MySQL
# DATABASE_URL=mysql://user:password@localhost:3306/keyloom
# SQLite
# DATABASE_URL=file:./dev.dbSetup and migrations
# Generate client
npx prisma generate
# Create initial migration
npx prisma migrate dev --name init
# Deploy prod migrations (CI/CD)
npx prisma migrate deploy# Generate client
pnpm prisma generate
# Create initial migration
pnpm prisma migrate dev --name init
# Deploy prod migrations (CI/CD)
pnpm prisma migrate deploy# Generate client
yarn prisma generate
# Create initial migration
yarn prisma migrate dev --name init
# Deploy prod migrations (CI/CD)
yarn prisma migrate deploy# Generate client
bunx prisma generate
# Create initial migration
bunx prisma migrate dev --name init
# Deploy prod migrations (CI/CD)
bunx prisma migrate deployUsing the adapter
import { PrismaClient } from "@prisma/client";
export const prisma = new PrismaClient();import { defineKeyloom } from "@keyloom/core";
import { PrismaAdapter } from "@keyloom/adapters";
import { prisma } from "@/lib/prisma";
export default defineKeyloom({
baseUrl: process.env.NEXT_PUBLIC_APP_URL!,
session: { strategy: "database", ttlMinutes: 30 },
adapter: PrismaAdapter(prisma),
providers: [],
rbac: { enabled: true, roles: { member: { permissions: ["read"] } } },
secrets: { authSecret: process.env.AUTH_SECRET! },
});Schema evolution examples
- Add a new user field
model User {
id String @id @default(cuid())
email String? @unique
name String?
plan String? // new column
}npx prisma migrate dev --name add-user-planpnpm prisma migrate dev --name add-user-planyarn prisma migrate dev --name add-user-planbunx prisma migrate dev --name add-user-plan- Add new index for performance
model Session {
id String @id @default(cuid())
userId String
expiresAt DateTime
@@index([userId, expiresAt])
}Configuration per database
- PostgreSQL: default; supports JSON, indexes, cascades
- MySQL: adjust provider to
mysql; long text columns may need@db.LongText - SQLite: great for local dev; avoid in production unless requirements are minimal
Performance tips
- Add indexes on
sessions(userId, expiresAt),accounts(provider, providerAccountId) - Use short session TTLs; rely on JWT strategy to reduce DB load
- Use connection pooling (pgBouncer) in serverful; Data Proxy/HTTP in serverless
- Avoid heavy queries in auth flows; keep sign-in path fast
Error handling & troubleshooting
P2002 Unique constraint failed (email): allowemailto be nullable and ensure uniqueness only when presentP1001 Can’t reach database: checkDATABASE_URL, network, and authmigrate devprompts drift: runprisma db pullor align migrations- Session missing after login: check cookie domain/path and TTL; confirm
session.strategy: "database"
Security notes
- Store only hashed verification and refresh tokens
- Use
onDelete: Cascadeto prevent orphaned rows when deleting users - Keep
AUTH_SECRETin a secret manager; never commit.env
See also
- Adapters overview: /docs/adapters/overview
- Installation: /docs/getting-started/installation
- Security: /docs/security/overview
Next steps
- Run migrations, wire the adapter, test the sign-in flow
- Add indices and tune queries with Prisma Client logs
How is this guide?