Skip to content

Prisma ​

prismanextjsotelpostgresql

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

query metric