5.9 KiB
Common Scenarios
Real-world scenarios and solutions for PrismaClient singleton pattern.
Scenario 1: Converting Existing Codebase
Current state: Multiple files create their own PrismaClient
Steps:
- Create central singleton:
lib/db.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
- Use Grep to find all
new PrismaClient()calls:
grep -rn "new PrismaClient()" --include="*.ts" --include="*.js" .
- Replace with imports from
lib/db.ts:
Before:
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export async function getUsers() {
return await prisma.user.findMany()
}
After:
import { prisma } from '@/lib/db'
export async function getUsers() {
return await prisma.user.findMany()
}
-
Remove old instantiations
-
Validate with grep (should find only one instance):
grep -rn "new PrismaClient()" --include="*.ts" --include="*.js" . | wc -l
Expected: 1
Scenario 2: Next.js Application
Setup:
-
Create
lib/prisma.tswith global singleton pattern -
Import in Server Components:
import { prisma } from '@/lib/prisma'
export default async function UsersPage() {
const users = await prisma.user.findMany()
return <UserList users={users} />
}
- Import in Server Actions:
'use server'
import { prisma } from '@/lib/prisma'
export async function createUser(formData: FormData) {
const email = formData.get('email') as string
return await prisma.user.create({ data: { email } })
}
- Import in Route Handlers:
import { NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
export async function GET() {
const users = await prisma.user.findMany()
return NextResponse.json(users)
}
- Set
connection_limit=1in DATABASE_URL for Vercel:
DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=1"
Validation:
- Hot reload shouldn't create new connections
- No P1017 errors in development
- Production deployments handle concurrent requests
Scenario 3: Encountering P1017 Errors
Symptoms:
- "Can't reach database server" errors
- "Too many connections" in database logs
- Intermittent connection failures
- Error code: P1017
Diagnosis:
- Grep codebase for
new PrismaClient():
grep -rn "new PrismaClient()" --include="*.ts" --include="*.js" .
- Check count of instances:
grep -rn "new PrismaClient()" --include="*.ts" --include="*.js" . | wc -l
If > 1: Multiple instance problem
- Review connection pool configuration:
grep -rn "connection_limit" .env* schema.prisma
If missing in serverless: Misconfiguration problem
Fix:
-
Implement singleton pattern (see Scenario 1)
-
Configure connection_limit for serverless:
Development (.env.local):
DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=10"
Production (Vercel):
DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=1"
- Monitor connection count after deployment:
SELECT count(*) FROM pg_stat_activity WHERE datname = 'your_database';
Expected: Should stabilize at reasonable number (not growing)
Scenario 4: Multiple Files Creating Clients
Problem: Different service files create their own clients
Before:
services/users.ts:
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export async function getUsers() {
return await prisma.user.findMany()
}
services/posts.ts:
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export async function getPosts() {
return await prisma.post.findMany()
}
Problems:
- Two separate connection pools
- Doubled memory usage
- Doubled connection count
- Multiplies with every service file
After:
lib/db.ts:
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export default prisma
services/users.ts:
import prisma from '@/lib/db'
export async function getUsers() {
return await prisma.user.findMany()
}
services/posts.ts:
import prisma from '@/lib/db'
export async function getPosts() {
return await prisma.post.findMany()
}
Result:
- Single connection pool shared across services
- Reduced memory usage
- Stable connection count
Connection Pool Configuration
The singleton pattern works with proper pool configuration:
Default pool size: 10 connections per PrismaClient
Serverless (Vercel, Lambda):
DATABASE_URL="postgresql://user:pass@host/db?connection_limit=1"
Traditional servers:
Calculate: connection_limit = (num_instances * 2) + 1
- 1 server = 3 connections
- 2 servers = 5 connections
- 4 servers = 9 connections
Development:
Default (10) is fine since only one developer instance runs.
Example configuration per environment:
const connectionLimit = process.env.NODE_ENV === 'production'
? 1
: 10
export const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL + `?connection_limit=${connectionLimit}`
}
}
})
Why This Matters
Real-world impact from stress testing:
- 80% of agents created multiple instances
- 100% of those would fail in production under load
- P1017 errors in serverless after ~10 concurrent requests
- Memory leaks from abandoned connection pools
- Database locked out teams during testing
The singleton pattern prevents all of these issues.
Use this pattern always, even if your app is small. It becomes critical as you scale, and retrofitting is painful.