00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Best Practice

Connection Pooling

In produzione, gestire correttamente le connessioni al database e’ fondamentale.

Configurazione del Pool

# Limita il numero di connessioni
DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=10&pool_timeout=30"

Singleton in Applicazioni Long-Running

// lib/prisma.ts
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', 'warn', 'error']
      : ['error'],
  })

if (process.env.NODE_ENV !== 'production') {
  globalForPrisma.prisma = prisma
}

Piattaforme Serverless

Per Vercel, AWS Lambda e simili, usa un connection pooler esterno:

datasource db {
  provider  = "postgresql"
  url       = env("DATABASE_URL")       // URL con PgBouncer/Supabase Pooler
  directUrl = env("DIRECT_URL")         // URL diretto per le migrazioni
}

Il Problema N+1

Il problema N+1 e’ uno dei piu’ comuni con gli ORM: si esegue 1 query per ottenere una lista, poi N query aggiuntive per ogni elemento.

Esempio del Problema

// SBAGLIATO: N+1 query
const users = await prisma.user.findMany()

for (const user of users) {
  // Ogni iterazione esegue una query separata!
  const posts = await prisma.post.findMany({
    where: { authorId: user.id },
  })
  console.log(`${user.name}: ${posts.length} post`)
}

Soluzione con include

// CORRETTO: una singola query con JOIN
const users = await prisma.user.findMany({
  include: {
    posts: true,
  },
})

users.forEach((user) => {
  console.log(`${user.name}: ${user.posts.length} post`)
})

Soluzione con _count

Se ti serve solo il conteggio:

// OTTIMALE: usa _count invece di caricare tutti i record
const users = await prisma.user.findMany({
  include: {
    _count: { select: { posts: true } },
  },
})

users.forEach((user) => {
  console.log(`${user.name}: ${user._count.posts} post`)
})

Query Optimization

Seleziona Solo i Campi Necessari

// SBAGLIATO: carica tutti i campi
const users = await prisma.user.findMany()

// CORRETTO: carica solo cio' che serve
const users2 = await prisma.user.findMany({
  select: {
    id: true,
    name: true,
    email: true,
  },
})

Usa gli Indici

Aggiungi indici sui campi usati frequentemente nei filtri:

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  authorId  Int
  category  String
  published Boolean  @default(false)
  createdAt DateTime @default(now())

  @@index([authorId])
  @@index([category])
  @@index([published, createdAt])  // Indice composto
}

Pagination Efficiente

// Per grandi dataset, preferisci cursor pagination
const posts = await prisma.post.findMany({
  take: 20,
  skip: 1,
  cursor: { id: lastId },
  orderBy: { id: 'asc' },
})

Logging

Prisma supporta diversi livelli di log per monitorare le query.

Configurazione Base

const prisma = new PrismaClient({
  log: ['query', 'info', 'warn', 'error'],
})

Log con Eventi

const prisma = new PrismaClient({
  log: [
    { level: 'query', emit: 'event' },
    { level: 'error', emit: 'stdout' },
    { level: 'warn', emit: 'stdout' },
  ],
})

// Ascolta le query per monitorare le performance
prisma.$on('query', (e) => {
  console.log(`Query: ${e.query}`)
  console.log(`Durata: ${e.duration}ms`)
  console.log(`Parametri: ${e.params}`)

  // Avviso per query lente
  if (e.duration > 500) {
    console.warn(`QUERY LENTA (${e.duration}ms): ${e.query}`)
  }
})

Error Handling

Gestisci correttamente i diversi tipi di errore Prisma:

import { Prisma } from '@prisma/client'

async function createUser(email: string, name: string) {
  try {
    return await prisma.user.create({
      data: { email, name },
    })
  } catch (error) {
    if (error instanceof Prisma.PrismaClientKnownRequestError) {
      switch (error.code) {
        case 'P2002':
          throw new Error(`Il campo ${error.meta?.target} e' gia' in uso`)
        case 'P2025':
          throw new Error('Record non trovato')
        case 'P2003':
          throw new Error('Violazione vincolo di chiave esterna')
        default:
          throw new Error(`Errore database: ${error.code}`)
      }
    }

    if (error instanceof Prisma.PrismaClientValidationError) {
      throw new Error('Dati non validi: controlla i campi inviati')
    }

    if (error instanceof Prisma.PrismaClientInitializationError) {
      throw new Error('Impossibile connettersi al database')
    }

    throw error
  }
}

Codici di Errore Comuni

Codice Significato
P2000 Valore troppo lungo per il campo
P2001 Record cercato nella condizione WHERE non esiste
P2002 Violazione vincolo di unicita’
P2003 Violazione chiave esterna
P2025 Record non trovato (update/delete)
P2034 Conflitto nella transazione

Prisma in Produzione

Checklist per il Deploy

# 1. Genera il client (aggiungi in postinstall)
npx prisma generate

# 2. Applica le migrazioni
npx prisma migrate deploy

# 3. Non usare MAI in produzione:
#    - prisma migrate dev (puo' resettare i dati)
#    - prisma db push (senza storia delle migrazioni)
#    - prisma studio (interfaccia non protetta)

Variabili d’Ambiente

# Produzione
DATABASE_URL="postgresql://user:pass@prod-host:5432/myapp?sslmode=require&connection_limit=15"

Health Check

// Verifica che il database sia raggiungibile
export async function checkDatabaseHealth(): Promise<boolean> {
  try {
    await prisma.$queryRaw`SELECT 1`
    return true
  } catch {
    return false
  }
}

Monitoraggio

Metriche delle Query

// Middleware per tracciare le performance
prisma.$use(async (params, next) => {
  const start = Date.now()
  const result = await next(params)
  const duration = Date.now() - start

  console.log(`${params.model}.${params.action} - ${duration}ms`)

  if (duration > 1000) {
    console.warn(`Query lenta: ${params.model}.${params.action} (${duration}ms)`)
  }

  return result
})

Soft Delete con Middleware

// Middleware per implementare soft-delete globale
prisma.$use(async (params, next) => {
  // Intercetta le operazioni delete e trasformale in update
  if (params.action === 'delete') {
    params.action = 'update'
    params.args.data = { deletedAt: new Date() }
  }

  // Filtra automaticamente i record eliminati
  if (params.action === 'findMany' || params.action === 'findFirst') {
    if (!params.args) params.args = {}
    if (!params.args.where) params.args.where = {}
    params.args.where.deletedAt = null
  }

  return next(params)
})