Prisma ORM Patterns

Other

Use this skill when working with Prisma ORM in Node.js/TypeScript projects. Covers schema design, migrations, query optimization, relations, transactions, and best practices for production-ready database interactions with Prisma 5+.

Install

openclaw skills install prisma-patterns

Prisma ORM Patterns

When to Use

  • Designing or migrating a Prisma schema
  • Writing complex queries with relations, filtering, or pagination
  • Handling transactions and error scenarios
  • Optimizing N+1 queries and performance
  • Setting up Prisma in monorepos or serverless environments

Core Workflow

1. Schema Design Principles

// schema.prisma
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())
  updatedAt DateTime @updatedAt

  posts     Post[]
  profile   Profile?

  @@index([email])
  @@map("users")
}

model Post {
  id        String   @id @default(cuid())
  title     String
  content   String?
  published Boolean  @default(false)
  authorId  String
  author    User     @relation(fields: [authorId], references: [id], onDelete: Cascade)

  tags      Tag[]    @relation("PostTags")

  @@index([authorId, published])
  @@map("posts")
}

2. Migration Workflow

# Development: auto-apply
npx prisma migrate dev --name add_user_profile

# Production: generate SQL only, review, then deploy
npx prisma migrate deploy

# Reset dev database
npx prisma migrate reset

# Introspect existing DB
npx prisma db pull

3. Client Initialization (Singleton Pattern)

// lib/prisma.ts
import { PrismaClient } from '@prisma/client'

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined
}

export const prisma =
  globalForPrisma.prisma ??
  new PrismaClient({
    log: process.env.NODE_ENV === 'development'
      ? ['query', 'error', 'warn']
      : ['error'],
  })

if (process.env.NODE_ENV !== 'production') {
  globalForPrisma.prisma = prisma
}

4. Common Query Patterns

// Paginated query with relations
const getPostsPage = async (page: number, limit = 10) => {
  const [posts, total] = await prisma.$transaction([
    prisma.post.findMany({
      where: { published: true },
      include: {
        author: { select: { id: true, name: true } },
        _count: { select: { tags: true } },
      },
      orderBy: { createdAt: 'desc' },
      skip: (page - 1) * limit,
      take: limit,
    }),
    prisma.post.count({ where: { published: true } }),
  ])
  return { posts, total, pages: Math.ceil(total / limit) }
}

// Upsert pattern
const upsertUser = async (email: string, name: string) => {
  return prisma.user.upsert({
    where: { email },
    update: { name },
    create: { email, name },
  })
}

// Avoid N+1: use include vs separate queries
const postsWithAuthors = await prisma.post.findMany({
  include: { author: true }, // Single JOIN query, not N+1
})

5. Transactions

// Interactive transaction (recommended for complex logic)
const transferCredits = async (fromId: string, toId: string, amount: number) => {
  return prisma.$transaction(async (tx) => {
    const from = await tx.user.findUniqueOrThrow({ where: { id: fromId } })
    if (from.credits < amount) throw new Error('Insufficient credits')

    await tx.user.update({
      where: { id: fromId },
      data: { credits: { decrement: amount } },
    })
    await tx.user.update({
      where: { id: toId },
      data: { credits: { increment: amount } },
    })
  })
}

6. Error Handling

import { Prisma } from '@prisma/client'

const safeCreate = async (data: Prisma.UserCreateInput) => {
  try {
    return await prisma.user.create({ data })
  } catch (e) {
    if (e instanceof Prisma.PrismaClientKnownRequestError) {
      if (e.code === 'P2002') {
        throw new Error(`Unique constraint violated: ${e.meta?.target}`)
      }
    }
    throw e
  }
}

Best Practices

  • Always use select to limit returned fields in production queries
  • Add @@index for frequently filtered/sorted columns
  • Use findUniqueOrThrow / findFirstOrThrow to avoid null checks
  • Prefer $transaction for multi-step operations
  • Enable query logging in development only
  • Use connection pooling (PgBouncer / Prisma Accelerate) in serverless