Database management with Prisma ORM including schema modeling, migrations, and query optimization. Use when working with database schemas, managing data models, running migrations, or optimizing database queries.
This skill covers best practices for using Prisma ORM effectively, including schema design, migrations, and query optimization.
Safely manage database schema changes with reproducible migrations.
Modify Schema
prisma/schema.prisma with new models or fieldsCreate Migration
pnpm prisma migrate dev --name add_feature_name
Review Migration
prisma/migrations/<timestamp>_<name>/migration.sqlPush to Production
pnpm prisma migrate deploy
// Adding a new model
model Quiz {
id String @id @default(cuid())
title String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// Adding a relationship
model Question {
id String @id @default(cuid())
quiz Quiz @relation(fields: [quizId], references: [id], onDelete: Cascade)
quizId String
}
// Adding a unique constraint
model User {
id String @id @default(cuid())
email String @unique
name String
}
// Adding an index for query performance
model Interview {
id String @id @default(cuid())
candidateId String
createdAt DateTime @default(now())
@@index([candidateId])
@@index([createdAt])
}
Design effective database schemas that support application requirements efficiently.
Naming Conventions
Relationships
// One-to-Many
model User {
id String @id @default(cuid())
interviews Interview[]
}
model Interview {
id String @id @default(cuid())
user User @relation(fields: [userId], references: [id])
userId String
}
// Many-to-Many (with join table)
model Quiz {
id String @id @default(cuid())
questions QuizQuestion[]
}
model Question {
id String @id @default(cuid())
quizzes QuizQuestion[]
}
model QuizQuestion {
id String @id @default(cuid())
quiz Quiz @relation(fields: [quizId], references: [id], onDelete: Cascade)
quizId String
question Question @relation(fields: [questionId], references: [id], onDelete: Cascade)
questionId String
order Int
@@unique([quizId, questionId])
}
Field Types
@db.Text for large text fieldsJson type for flexible data structuresTimestamps and Metadata
model Entity {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdBy String?
}
model Candidate {
id String @id @default(cuid())
email String @unique
name String
createdAt DateTime @default(now())
// Single field index
@@index([email])
// Composite index for common queries
@@index([createdAt, id])
// Full-text search index (PostgreSQL)
@@fulltext([name])
}
Write efficient database queries that minimize load and improve application performance.
Use select() to Fetch Only Needed Fields
// Bad - fetches all fields
const user = await prisma.user.findUnique({
where: { id: "user-1" },
});
// Good - fetch only needed fields
const user = await prisma.user.findUnique({
where: { id: "user-1" },
select: {
id: true,
email: true,
name: true,
},
});
Batch Queries Efficiently
// Avoid N+1 queries
const users = await prisma.user.findMany();
for (const user of users) {
const interviews = await prisma.interview.findMany({
where: { userId: user.id },
}); // N+1 problem!
}
// Solution - use include or nested queries
const users = await prisma.user.findMany({
include: {
interviews: true,
},
});
Use Relations with include() or select()
const quiz = await prisma.quiz.findUnique({
where: { id: "quiz-1" },
include: {
quizQuestions: {
include: {
question: true,
},
orderBy: { order: "asc" },
},
},
});
Pagination for Large Result Sets
const quizzes = await prisma.quiz.findMany({
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: { createdAt: "desc" },
});
Aggregations
const stats = await prisma.interview.aggregate({
where: { candidateId: "candidate-1" },
_count: true,
_avg: { score: true },
});
Raw Queries for Complex Operations
const results = await prisma.$queryRaw`
SELECT u.*, COUNT(i.id) as interview_count
FROM User u
LEFT JOIN Interview i ON u.id = i.userId
GROUP BY u.id
`;
select() instead of fetching entire recordsdistinct() to avoid duplicate resultsCombine Prisma queries with cache components in server components, and use server actions for mutations:
// Data fetching (in server component or cached function)
"use cache";
import { cacheLife, cacheTag } from "next/cache";
export async function getCachedQuiz(id: string) {
cacheLife({ max: 3600 });
cacheTag("quizzes");
return await prisma.quiz.findUnique({
where: { id },
include: {
quizQuestions: {
include: { question: true },
orderBy: { order: "asc" },
},
},
});
}
// Mutations (in server action, NOT API route)
("use server");
import { updateTag } from "@/lib/utils/cache-utils";
export async function updateQuizAction(id: string, data: QuizInput) {
const user = await requireUser();
const quiz = await prisma.quiz.update({
where: { id },
data,
});
// Invalidate cache after mutation
updateTag("quizzes");
return { success: true, data: quiz };
}
When working with Prisma: