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

239 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Serverless Pattern
Serverless environments require special handling due to cold starts, connection pooling, and function lifecycle constraints.
## Next.js App Router (Vercel)
**File: `lib/prisma.ts`**
```typescript
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
```
**Environment Configuration (.env):**
```
DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=1&pool_timeout=10"
```
**Why connection_limit=1:**
- Each serverless function instance gets ONE connection
- Multiple function instances = multiple connections
- Prevents pool exhaustion with many concurrent requests
- Vercel scales to hundreds of instances automatically
**Usage in Server Components:**
```typescript
import { prisma } from '@/lib/prisma'
export default async function UsersPage() {
const users = await prisma.user.findMany()
return <UserList users={users} />
}
```
**Usage 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 }
})
}
```
**Usage 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)
}
```
**Key Points:**
- Never create PrismaClient in component files
- Import singleton from `lib/prisma.ts`
- Global pattern survives hot reload
- Connection limit prevents pool exhaustion
---
## AWS Lambda
**File: `lib/db.ts`**
```typescript
import { PrismaClient } from '@prisma/client'
let prisma: PrismaClient
if (!global.prisma) {
global.prisma = new PrismaClient({
log: ['error', 'warn']
})
}
prisma = global.prisma
export default prisma
```
**Lambda Handler:**
```typescript
import prisma from './lib/db'
export async function handler(event: any) {
const users = await prisma.user.findMany()
return {
statusCode: 200,
body: JSON.stringify(users)
}
}
```
**Environment Variables (Lambda):**
```
DATABASE_URL=postgresql://user:pass@host:5432/db?connection_limit=1&pool_timeout=10&connect_timeout=10
```
**Lambda-Specific Considerations:**
- Lambda reuses container for warm starts
- Global singleton survives across invocations
- First invocation creates client (cold start)
- Subsequent invocations reuse client (warm starts)
- No need to disconnect (Lambda freezes container)
---
## Connection Pool Calculation for Serverless
**Formula:**
```
max_connections = (max_concurrent_functions * connection_limit) + buffer
```
**Example (Vercel):**
- Max concurrent functions: 100
- Connection limit per function: 1
- Buffer: 10
**Result:** Need 110 database connections
**Recommended DATABASE_URL for Vercel:**
```
postgresql://user:pass@host:5432/db?connection_limit=1&pool_timeout=10
```
**Why pool_timeout=10:**
- Prevents long waits for connections
- Fails fast if pool exhausted
- User gets error instead of timeout
---
## Anti-Pattern: Multiple Files Creating Clients
**WRONG - Each file creates its own:**
**`app/api/users/route.ts`:**
```typescript
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export async function GET() {
return Response.json(await prisma.user.findMany())
}
```
**`app/api/posts/route.ts`:**
```typescript
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export async function GET() {
return Response.json(await prisma.post.findMany())
}
```
**Problems in Serverless:**
- Each route = separate client = separate pool
- 2 routes × 50 function instances × 10 connections = 1000 connections!
- Database exhausted under load
- P1017 errors inevitable
**Fix - Central singleton:**
**`lib/prisma.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
```
**`app/api/users/route.ts`:**
```typescript
import { prisma } from '@/lib/prisma'
export async function GET() {
return Response.json(await prisma.user.findMany())
}
```
**`app/api/posts/route.ts`:**
```typescript
import { prisma } from '@/lib/prisma'
export async function GET() {
return Response.json(await prisma.post.findMany())
}
```
**Result:**
- 50 function instances × 1 connection = 50 connections
- Sustainable and scalable
- No P1017 errors