Files
gh-djankies-claude-configs-…/skills/creating-client-singletons/references/common-scenarios.md
2025-11-29 18:22:25 +08:00

311 lines
5.9 KiB
Markdown

# 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:**
1. Create central singleton: `lib/db.ts`
```typescript
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
```
2. Use Grep to find all `new PrismaClient()` calls:
```bash
grep -rn "new PrismaClient()" --include="*.ts" --include="*.js" .
```
3. Replace with imports from `lib/db.ts`:
**Before:**
```typescript
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export async function getUsers() {
return await prisma.user.findMany()
}
```
**After:**
```typescript
import { prisma } from '@/lib/db'
export async function getUsers() {
return await prisma.user.findMany()
}
```
4. Remove old instantiations
5. Validate with grep (should find only one instance):
```bash
grep -rn "new PrismaClient()" --include="*.ts" --include="*.js" . | wc -l
```
Expected: `1`
---
## Scenario 2: Next.js Application
**Setup:**
1. Create `lib/prisma.ts` with global singleton pattern
2. Import in Server Components:
```typescript
import { prisma } from '@/lib/prisma'
export default async function UsersPage() {
const users = await prisma.user.findMany()
return <UserList users={users} />
}
```
3. Import in Server Actions:
```typescript
'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 } })
}
```
4. Import in Route Handlers:
```typescript
import { NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
export async function GET() {
const users = await prisma.user.findMany()
return NextResponse.json(users)
}
```
5. Set `connection_limit=1` in 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:**
1. Grep codebase for `new PrismaClient()`:
```bash
grep -rn "new PrismaClient()" --include="*.ts" --include="*.js" .
```
2. Check count of instances:
```bash
grep -rn "new PrismaClient()" --include="*.ts" --include="*.js" . | wc -l
```
If > 1: Multiple instance problem
3. Review connection pool configuration:
```bash
grep -rn "connection_limit" .env* schema.prisma
```
If missing in serverless: Misconfiguration problem
**Fix:**
1. Implement singleton pattern (see Scenario 1)
2. 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"
```
3. Monitor connection count after deployment:
```sql
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`:**
```typescript
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export async function getUsers() {
return await prisma.user.findMany()
}
```
**`services/posts.ts`:**
```typescript
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`:**
```typescript
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export default prisma
```
**`services/users.ts`:**
```typescript
import prisma from '@/lib/db'
export async function getUsers() {
return await prisma.user.findMany()
}
```
**`services/posts.ts`:**
```typescript
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:**
```typescript
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.