Prisma
Schema Best Practice
@auth/prisma-adapter requires some models for PostgreSQL but some models like Session, VerificationToken are unused when using JWT strategy of next-auth. This template pre-deletes them.
prisma
// https://pris.ly/d/prisma-schema
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["fullTextSearchPostgres"]
output = "../../src/app/__generated__/prisma"
moduleFormat = "esm"
}prisma
enum Role {
USER
ADMIN
}
// https://authjs.dev/getting-started/adapters/prisma#schema
model Account {
id String @id @default(cuid())
userId String @map("user_id")
type String
provider String
providerAccountId String @map("provider_account_id")
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([provider, providerAccountId])
@@map("accounts")
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime? @map("email_verified")
image String?
accounts Account[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// https://authjs.dev/guides/basics/role-based-access-control
role Role @default(USER)
// start: sample
items Item[]
// end: sample
stripeId String? @unique @map("stripe_id") // cus_XXXX
subscriptions Subscription[]
@@map("users")
}
model Subscription {
id String @id @default(cuid())
subscriptionId String @unique @map("subscription_id") // sub_XXXX
status String
currentPeriodEnd DateTime? @map("current_period_end")
cancelAtPeriodEnd Boolean @default(false) @map("cancel_at_period_end")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
userId String @map("user_id")
user User @relation(fields: [userId], references: [id])
@@map("subscriptions")
}prisma
model Item {
id String @id @default(cuid())
content String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String @map("user_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("items")
}Making single client instance in Development Best Practice
In development, prisma requires avoiding multiple Prisma Client instances but Hot Module Replacement creates them. So this template prepares code to resolve this issue.
ts
// https://www.prisma.io/docs/orm/more/help-and-troubleshooting/help-articles/nextjs-prisma-client-dev-practices
import { PrismaClient } from "../__generated__/prisma";
import { createDBUrl } from "../_utils/db";
function prismaClientSingleton() {
return new PrismaClient({
datasources: {
db: {
url: createDBUrl({}),
},
},
});
}
// biome-ignore lint: Do not shadow the global "globalThis" property.
declare const globalThis: {
prismaGlobal: ReturnType<typeof prismaClientSingleton>;
} & typeof global;
export const prisma = globalThis.prismaGlobal ?? prismaClientSingleton();
if (process.env.NODE_ENV !== "production") {
globalThis.prismaGlobal = prisma;
}ts
export function createDBUrl({
user = process.env.DATABASE_USER,
password = process.env.DATABASE_PASSWORD,
host = process.env.DATABASE_HOST,
port = Number(process.env.DATABASE_PORT),
db = process.env.DATABASE_DB,
schema = process.env.DATABASE_SCHEMA,
}: {
user?: string;
password?: string;
host?: string;
port?: number;
db?: string;
schema?: string;
}) {
return `postgresql://${user}:${password}@${host}:${port}/${db}?schema=${schema}`;
}> `datasources.db.url` is always set for parallel execution by testing
Observability Best Practice
Prisma provides OpenTelemetry tracing feature. This template uses @prisma/instrumentation and you can view Prisma Query, Engine, and Database Query on Jeager via otel-collector. Learn more here
