Prisma Core Knowledge
Deep Knowledge: Use
mcp__documentation__fetch_docswith technology:prismafor comprehensive documentation.
When NOT to Use This Skill
- Raw SQL Operations: Use the
database-queryMCP server for direct SQL queries and database inspection - Other ORMs: Use appropriate skills (
drizzle,typeorm,sqlalchemy) for other ORM frameworks - Database Design: Consult
architect-expertorsql-expertfor database architecture decisions - Performance Profiling: Use
performance-profilerMCP for query performance analysis - Migration Rollbacks: Engage
devops-expertfor production migration strategies
Schema Definition
// schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
}
CRUD Operations
// Create
const user = await prisma.user.create({
data: { email: 'user@example.com', name: 'John' }
});
// Read
const users = await prisma.user.findMany({
where: { email: { contains: '@example.com' } },
include: { posts: true },
orderBy: { createdAt: 'desc' },
take: 10
});
const user = await prisma.user.findUnique({
where: { id: 1 }
});
// Update
await prisma.user.update({
where: { id: 1 },
data: { name: 'Jane' }
});
// Delete
await prisma.user.delete({ where: { id: 1 } });
Relations
// Create with relation
await prisma.user.create({
data: {
email: 'user@example.com',
posts: {
create: [
{ title: 'First Post' },
{ title: 'Second Post' }
]
}
}
});
// Query with relation
const userWithPosts = await prisma.user.findUnique({
where: { id: 1 },
include: { posts: { where: { published: true } } }
});
Commands
npx prisma init
npx prisma migrate dev --name init
npx prisma generate
npx prisma studio
Anti-Patterns
| Anti-Pattern | Why It's Bad | Better Approach |
|---|---|---|
synchronize: true in production | Can cause data loss, no migration history | Use prisma migrate deploy |
No select or include on queries | Fetches all fields, performance waste | Select only needed fields |
N+1 queries without include | Multiple round trips to database | Use include or select with nested relations |
| Missing indexes on foreign keys | Slow joins and lookups | Add @@index([foreignKeyField]) |
| Hardcoded connection strings | Security risk, no environment flexibility | Use env("DATABASE_URL") |
| No transaction for multi-step operations | Data inconsistency risk | Wrap in $transaction |
Using findMany() without pagination | Memory issues with large datasets | Add take and skip or cursor-based pagination |
| Ignoring Prisma error codes | Poor user experience, security risks | Handle PrismaClientKnownRequestError codes |
| No connection pool limits | Connection exhaustion | Configure connection_limit in URL |
| Running migrations manually in prod | Human error, no audit trail | Automate via CI/CD with migrate deploy |
Quick Troubleshooting
| Issue | Likely Cause | Solution |
|---|---|---|
| "Can't reach database server" | Wrong DATABASE_URL or DB not running | Verify connection string, check DB status |
| "Unique constraint failed" (P2002) | Duplicate value in unique field | Check for existing record, handle error gracefully |
| "Foreign key constraint failed" (P2003) | Referenced record doesn't exist | Verify related record exists before insertion |
| "Record not found" (P2025) | Query returned no results | Use findUnique with null check or handle error |
| Slow queries | Missing indexes, N+1 problem | Add indexes, use include for relations |
| "Type 'X' is not assignable" | Generated client out of sync | Run npx prisma generate |
| Migration conflicts | Multiple developers creating migrations | Coordinate migrations, use git properly |
| Connection pool exhausted | Too many concurrent connections | Increase connection_limit or use Prisma Accelerate |
| "Client is not connected" | PrismaClient not initialized | Ensure new PrismaClient() before use |
| Schema drift | Manual DB changes not in schema | Use prisma db pull to sync or create migration |
Production Readiness
Security Configuration
// schema.prisma - Secure connection
datasource db {
provider = "postgresql"
url = env("DATABASE_URL") // Use env vars, never hardcode
}
// DATABASE_URL format with SSL
// postgresql://user:pass@host:5432/db?sslmode=require&sslcert=/path/to/client-cert.pem
// Secure PrismaClient initialization
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient({
log: process.env.NODE_ENV === 'production'
? ['error']
: ['query', 'info', 'warn', 'error'],
errorFormat: process.env.NODE_ENV === 'production' ? 'minimal' : 'pretty',
});
Connection Pooling
// schema.prisma - Connection pool settings
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
// For serverless (e.g., Vercel, AWS Lambda)
// Use Prisma Accelerate or PgBouncer
}
// Connection management
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL,
},
},
});
// Connection pool via URL params
// ?connection_limit=5&pool_timeout=10
// For serverless: use Prisma Accelerate
// DATABASE_URL="prisma://accelerate.prisma-data.net/?api_key=xxx"
Query Optimization
// Select only needed fields
const users = await prisma.user.findMany({
select: {
id: true,
email: true,
// Don't select unnecessary fields
},
});
// Use pagination
const users = await prisma.user.findMany({
take: 20,
skip: (page - 1) * 20,
cursor: lastId ? { id: lastId } : undefined,
});
// Batch operations for bulk inserts
await prisma.user.createMany({
data: users,
skipDuplicates: true,
});
// Use transactions for related operations
await prisma.$transaction([
prisma.order.create({ data: orderData }),
prisma.inventory.update({ where: { productId }, data: { quantity: { decrement: 1 } } }),
]);
// Interactive transactions with timeout
await prisma.$transaction(async (tx) => {
const user = await tx.user.create({ data: userData });
await tx.profile.create({ data: { userId: user.id, ...profileData } });
return user;
}, {
maxWait: 5000, // Wait for connection
timeout: 10000, // Transaction timeout
});
Error Handling
import { Prisma } from '@prisma/client';
async function createUser(data: UserInput) {
try {
return await prisma.user.create({ data });
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
// Unique constraint violation
if (error.code === 'P2002') {
throw new ConflictError(`User with this ${error.meta?.target} already exists`);
}
// Foreign key constraint violation
if (error.code === 'P2003') {
throw new BadRequestError('Related record not found');
}
// Record not found
if (error.code === 'P2025') {
throw new NotFoundError('Record not found');
}
}
throw error;
}
}
Migrations in Production
# Generate migration (development)
npx prisma migrate dev --name add_user_status
# Apply migrations (production)
npx prisma migrate deploy
# Reset database (DANGER - only for development)
npx prisma migrate reset
# Check migration status
npx prisma migrate status
# CI/CD migration workflow
jobs:
migrate:
steps:
- name: Apply migrations
run: npx prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
Monitoring Metrics
| Metric | Alert Threshold |
|---|---|
| Query duration p99 | > 100ms |
| Connection pool wait time | > 5s |
| Failed queries | > 1% |
| Connection pool exhaustion | Any occurrence |
| Slow queries count | > 10/min |
Logging & Tracing
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient({
log: [
{ level: 'query', emit: 'event' },
{ level: 'error', emit: 'stdout' },
],
});
// Log slow queries
prisma.$on('query', (e) => {
if (e.duration > 100) {
console.log(`Slow query (${e.duration}ms): ${e.query}`);
}
});
// OpenTelemetry integration
import { PrismaInstrumentation } from '@prisma/instrumentation';
registerInstrumentations({
instrumentations: [new PrismaInstrumentation()],
});
Graceful Shutdown
// Proper cleanup on shutdown
async function gracefulShutdown() {
await prisma.$disconnect();
process.exit(0);
}
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);
Checklist
- Database URL via environment variables
- SSL/TLS connection enabled
- Connection pooling configured
- Error handling for Prisma errors
- Migrations via CI/CD pipeline
- Select only needed fields
- Pagination on list queries
- Transactions for related operations
- Slow query logging
- Graceful shutdown handling
- OpenTelemetry tracing (optional)
- Prisma Accelerate for serverless (optional)