Prisma schema design patterns, relations (1:1, 1:N, M:N), migrations workflow, Prisma Client queries (findMany, create, update, transactions), seeding, type-safe queries, and performance optimization.
Prisma schema design patterns, relations (1:1, 1:N, M:N), migrations workflow, Prisma Client queries (findMany, create, update, transactions), seeding, type-safe queries, and performance optimization.
This skill provides comprehensive guidance for working with Prisma ORM, including schema design, relations, migrations, query patterns, transactions, seeding, and performance optimization.
// prisma/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[]
}
enum Role {
USER
ADMIN
MODERATOR
}
model User {
id String @id @default(cuid())
role Role @default(USER)
}
type Address {
street String
city String
zipCode String
country String
}
model User {
id String @id @default(cuid())
address Address?
}
model User {
id String @id @default(cuid())
posts Post[]
}
model Post {
id String @id @default(cuid())
title String
authorId String
author User @relation(fields: [authorId], references: [id])
}
model Post {
id String @id @default(cuid())
title String
tags PostTag[]
}
model Tag {
id String @id @default(cuid())
name String @unique
posts PostTag[]
}
model PostTag {
postId String
tagId String
post Post @relation(fields: [postId], references: [id])
tag Tag @relation(fields: [tagId], references: [id])
@@id([postId, tagId])
}
model User {
id String @id @default(cuid())
profile Profile?
}
model Profile {
id String @id @default(cuid())
bio String?
userId String @unique
user User @relation(fields: [userId], references: [id])
}
model User {
id String @id @default(cuid())
name String
managerId String?
manager User? @relation("UserManager", fields: [managerId], references: [id])
directReports User[] @relation("UserManager")
}
import { prisma } from '@/lib/prisma'
// Find many
const users = await prisma.user.findMany({
where: { role: 'ADMIN' },
orderBy: { createdAt: 'desc' },
})
// Find unique
const user = await prisma.user.findUnique({
where: { email: 'user@example.com' },
})
// Find first
const firstUser = await prisma.user.findFirst({
where: { name: { contains: 'John' } },
})
const userWithPosts = await prisma.user.findUnique({
where: { id: 'user-id' },
include: {
posts: {
where: { published: true },
orderBy: { createdAt: 'desc' },
},
},
})
const users = await prisma.user.findMany({
select: {
id: true,
name: true,
email: true,
// Exclude posts
},
})
// AND conditions
const users = await prisma.user.findMany({
where: {
AND: [
{ role: 'ADMIN' },
{ createdAt: { gte: new Date('2024-01-01') } },
],
},
})
// OR conditions
const users = await prisma.user.findMany({
where: {
OR: [
{ email: { contains: '@example.com' } },
{ name: { contains: 'Admin' } },
],
},
})
// NOT conditions
const users = await prisma.user.findMany({
where: {
NOT: {
role: 'ADMIN',
},
},
})
// Offset pagination
const users = await prisma.user.findMany({
skip: 10,
take: 20,
orderBy: { createdAt: 'desc' },
})
// Cursor pagination
const users = await prisma.user.findMany({
take: 20,
cursor: { id: 'last-user-id' },
skip: 1,
orderBy: { id: 'asc' },
})
// Single create
const user = await prisma.user.create({
data: {
email: 'user@example.com',
name: 'John Doe',
},
})
// Create with relation
const post = await prisma.post.create({
data: {
title: 'My Post',
author: {
connect: { id: 'user-id' },
},
},
})
// Create many
const users = await prisma.user.createMany({
data: [
{ email: 'user1@example.com', name: 'User 1' },
{ email: 'user2@example.com', name: 'User 2' },
],
skipDuplicates: true,
})
// Update single
const user = await prisma.user.update({
where: { id: 'user-id' },
data: {
name: 'Updated Name',
},
})
// Update many
const result = await prisma.user.updateMany({
where: { role: 'USER' },
data: { role: 'ADMIN' },
})
// Upsert
const user = await prisma.user.upsert({
where: { email: 'user@example.com' },
update: { name: 'Updated Name' },
create: {
email: 'user@example.com',
name: 'New User',
},
})
// Delete single
const user = await prisma.user.delete({
where: { id: 'user-id' },
})
// Delete many
const result = await prisma.user.deleteMany({
where: { role: 'GUEST' },
})
const result = await prisma.$transaction(async (tx) => {
const user = await tx.user.create({
data: {
email: 'user@example.com',
name: 'John Doe',
},
})
const post = await tx.post.create({
data: {
title: 'First Post',
authorId: user.id,
},
})
return { user, post }
})
const result = await prisma.$transaction(
async (tx) => {
const user = await tx.user.findUnique({
where: { id: 'user-id' },
})
if (!user) {
throw new Error('User not found')
}
const updatedUser = await tx.user.update({
where: { id: user.id },
data: { name: 'Updated Name' },
})
return updatedUser
},
{
maxWait: 5000,
timeout: 10000,
}
)
const [users, posts] = await prisma.$transaction([
prisma.user.findMany(),
prisma.post.findMany(),
])
const users = await prisma.$queryRaw`
SELECT * FROM "User" WHERE "role" = ${'ADMIN'}
`
// With Prisma types
const users = await prisma.$queryRaw<User[]>`
SELECT * FROM "User" WHERE "createdAt" > ${new Date('2024-01-01')}
`
const users = await prisma.$queryRawUnsafe(
'SELECT * FROM "User" WHERE "email" = $1',
'user@example.com'
)
npx prisma migrate dev --name add_user_role
npx prisma migrate deploy
npx prisma migrate reset
// prisma/migrations/20240101000000_add_user_role/migration.sql
ALTER TABLE "User" ADD COLUMN "role" TEXT NOT NULL DEFAULT 'USER';
// prisma/seed.ts
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
// Create users
const user1 = await prisma.user.upsert({
where: { email: 'alice@example.com' },
update: {},
create: {
email: 'alice@example.com',
name: 'Alice',
posts: {
create: {
title: 'First Post',
content: 'This is my first post',
},
},
},
})
const user2 = await prisma.user.upsert({
where: { email: 'bob@example.com' },
update: {},
create: {
email: 'bob@example.com',
name: 'Bob',
},
})
console.log({ user1, user2 })
}
main()
.catch((e) => {
console.error(e)
process.exit(1)
})
.finally(async () => {
await prisma.$disconnect()
})
{
"prisma": {
"seed": "ts-node prisma/seed.ts"
}
}
// Bad: Fetches all fields
const users = await prisma.user.findMany()
// Good: Select only needed fields
const users = await prisma.user.findMany({
select: {
id: true,
name: true,
email: true,
},
})
// Bad: Multiple queries
for (const id of userIds) {
await prisma.user.update({
where: { id },
data: { role: 'ADMIN' },
})
}
// Good: Single batch update
await prisma.user.updateMany({
where: { id: { in: userIds } },
data: { role: 'ADMIN' },
})
model User {
id String @id @default(cuid())
email String @unique
name String
@@index([name])
@@index([email, name])
}
// 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
prisma.$use(async (params, next) => {
const before = Date.now()
const result = await next(params)
const after = Date.now()
console.log(`Query ${params.model}.${params.action} took ${after - before}ms`)
return result
})
prisma.$use(async (params, next) => {
if (params.action === 'findMany' && params.model === 'User') {
params.args.where = {
...params.args.where,
deletedAt: null,
}
}
return next(params)
})
This skill should be used when strict adherence to the defined process is required.
Category:other