DEV Community

Juan Torchia
Juan Torchia Subscriber

Posted on • Originally published at juanchi.dev

Prisma 5 Prisma 6: los breaking changes que encontré en mi schema real y cómo los resolví sin romper producción

Prisma 5 → Prisma 6: los breaking changes que encontré en mi schema real y cómo los resolví sin romper producción

La solución correcta para migrar de Prisma 5 a Prisma 6 sin romper nada es no correr el upgrade el viernes. Sé que suena obvio. Pero el punto real es otro: Prisma 6 tiene cambios que el compilador de TypeScript no te va a gritar. Van a pasar silenciosamente, y vas a enterarte en runtime — o peor, en un resultado de query que parece correcto pero no lo es.

Mi tesis es esta: Prisma 6 es una mejora genuina en ergonomía y performance, pero hay tres cambios de comportamiento que requieren atención manual antes de hacer el upgrade. No son bugs — son decisiones deliberadas del equipo de Prisma que cambian cómo se comportan las queries relacionales, el cliente generado y las transacciones. Si no los conocés de antemano, te van a encontrar ellos a vos.

Lo que sigue es el análisis de esos tres cambios, con código representativo y el checklist que construí para no repetirlo.


Por qué Prisma 6 importa (y qué dice el anuncio oficial)

El anuncio oficial de Prisma — "What's new in Prisma 6" — tiene tres ejes centrales:

  1. Mejor performance — internals reescritos, query engine más eficiente.
  2. Más flexibilidad — soporte mejorado para múltiples providers y configuración del cliente.
  3. SQL type-safe — la nueva API prisma.$queryRawTyped con inferencia real.

Todo eso es real y bienvenido. Lo que el anuncio no dice con énfasis suficiente — y lo que te cuesta tiempo cuando hacés el upgrade sin leer la guía de migración completa — son los comportamientos que cambiaron silenciosamente.

Voy a cubrir los tres que más impactan en un stack Next.js 16 + Server Actions + PostgreSQL.


Cambio 1: selectRelationCount ya no es opt-in — cambió la forma de contar relaciones

En Prisma 5, si querías contar relaciones (por ejemplo, cuántos posts tiene un usuario) dentro de un select, tenías que habilitar el preview feature selectRelationCount en el schema:

// schema.prisma — Prisma 5
generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["selectRelationCount"]
}
Enter fullscreen mode Exit fullscreen mode

En Prisma 6, selectRelationCount pasó a ser GA y la flag de preview fue removida. Si la dejás en el schema, Prisma CLI te lanza un warning — o directamente un error dependiendo de la versión puntual. La funcionalidad sigue andando, pero el API cambió sutilmente en cómo se integra con include vs select.

// ✅ Prisma 5 — funcionaba con la preview feature activa
const usuarios = await prisma.usuario.findMany({
  select: {
    id: true,
    nombre: true,
    _count: {
      select: { posts: true }
    }
  }
})

// ✅ Prisma 6 — misma sintaxis, pero sin la flag en el schema
// Si la flag sigue presente, el CLI emite warning en generate
const usuarios = await prisma.usuario.findMany({
  select: {
    id: true,
    nombre: true,
    _count: {
      select: { posts: true }
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

Acción concreta: buscá todas las previewFeatures en el schema y verificá cuáles pasaron a GA en v6. La guía oficial lista cuáles ya no son preview. Removalas antes de correr prisma generate.


Cambio 2: el comportamiento de undefined en queries relacionales cambió

Este es el que más duele porque no hay error en tiempo de compilación. En Prisma 5, pasar undefined como valor en un where era ignorado — el filtro simplemente no se aplicaba. En Prisma 6, ese comportamiento fue estandarizado de forma más estricta: en algunos casos undefined sigue siendo ignorado, pero en otros — especialmente dentro de select anidados con relaciones opcionales — el comportamiento difiere según si el campo es nullable o no en el schema.

// ⚠️ Patrón peligroso en la transición Prisma 5 → 6
async function obtenerPosts(filtroCategoria?: string) {
  return await prisma.post.findMany({
    where: {
      // En Prisma 5: si filtroCategoria es undefined, este where era ignorado
      // En Prisma 6: el comportamiento depende del tipo del campo en el schema
      // Si 'categoria' es un campo opcional (String?), puede comportarse diferente
      categoria: filtroCategoria,
    },
    include: {
      autor: true
    }
  })
}
Enter fullscreen mode Exit fullscreen mode

El fix es explícito y más defensivo:

// ✅ Patrón seguro para Prisma 5 y 6
async function obtenerPosts(filtroCategoria?: string) {
  return await prisma.post.findMany({
    where: {
      // Construí el where condicionalmente — no dependas del comportamiento de undefined
      ...(filtroCategoria !== undefined && { categoria: filtroCategoria }),
    },
    include: {
      autor: true
    }
  })
}
Enter fullscreen mode Exit fullscreen mode

Lo incómodo de este cambio: el TypeScript types del cliente generado no cambian. String | undefined sigue siendo válido como tipo en el where. El compilador no te avisa nada. Tenés que buscarlo a mano o con tests de integración.

Mi punto acá: construir los objetos where condicionalmente no es un workaround — es la práctica correcta en cualquier versión de Prisma. Si tu codebase tiene muchos lugares donde pasás variables opcionales directo al where, este es el momento de limpiarlos.


Cambio 3: el cliente generado cambió y los imports directos de tipos pueden romperse

Prisma 6 reorganizó la estructura del cliente generado. Si en algún lugar de la codebase importás tipos directamente desde la carpeta .prisma/client o desde rutas internas del paquete (algo que no debería hacerse pero que aparece en tutoriales viejos), esos imports pueden romperse silenciosamente o con errores crípticos.

// ❌ Patrón frágil — importar desde rutas internas del cliente generado
// Esto podía funcionar en Prisma 5 pero es una API privada, no pública
import { Prisma } from '@prisma/client/edge'

// ✅ Importá desde el punto de entrada público siempre
import { Prisma, PrismaClient } from '@prisma/client'
Enter fullscreen mode Exit fullscreen mode

El caso más frecuente en Next.js 16 con Server Actions: usar el cliente de edge (@prisma/client/edge) para middleware o rutas que corren en el Edge Runtime. En Prisma 6, la configuración del cliente de edge fue unificada y la forma de instanciarlo cambió. La documentación oficial tiene el detalle actualizado, pero el error que vas a ver si no lo actualizás es genérico — algo del estilo "cannot find module" o "type is not assignable" que no señala directamente el problema.

// ✅ Prisma 6 con Next.js 16 — instancia única del cliente
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'

const globalForPrisma = global as unknown as { prisma: PrismaClient }

export const prisma =
  globalForPrisma.prisma ??
  new PrismaClient({
    // log solo en desarrollo — no expongas query logs en producción
    log: process.env.NODE_ENV === 'development' ? ['query', 'error'] : ['error'],
  })

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
Enter fullscreen mode Exit fullscreen mode

Este patrón no cambió entre v5 y v6, pero si lo tenías mal configurado (instancias múltiples, singleton roto), el upgrade es el momento de corregirlo.


Errores comunes en la migración — los gotchas que más aparecen

Gotcha 1: correr prisma db push sin leer el output completo.

Prisma 6 puede generar migraciones ligeramente diferentes para el mismo schema si hay campos con tipos que cambiaron internamente (como algunos tipos de DateTime con precisión). Revisá el diff de migración antes de aplicarlo.

Gotcha 2: asumir que prisma migrate dev y prisma migrate deploy se comportan igual.

migrate dev puede hacer cosas adicionales (como resetear la DB en conflictos). En un entorno que se parece a producción, usá siempre migrate deploy y revisá el estado con prisma migrate status antes.

# Verificá el estado de migraciones antes del upgrade
npx prisma migrate status

# Generá el cliente después de actualizar la versión
npx prisma generate

# Revisá las diferencias de schema sin aplicar nada
npx prisma migrate diff \
  --from-schema-datasource prisma/schema.prisma \
  --to-schema-datamodel prisma/schema.prisma \
  --script
Enter fullscreen mode Exit fullscreen mode

Gotcha 3: no actualizar las devDependencies junto con @prisma/client.

prisma (el CLI) y @prisma/client tienen que estar en la misma versión mayor. Si actualizás uno y no el otro, vas a tener errores de generación de cliente que son difíciles de diagnosticar.

# Actualizá ambos juntos siempre
npm install prisma@6 @prisma/client@6

# O con pnpm
pnpm add prisma@6 @prisma/client@6
Enter fullscreen mode Exit fullscreen mode

Gotcha 4: el comportamiento de transacciones con $transaction y timeouts.

Prisma 6 ajustó los defaults de timeout en transacciones interactivas. Si tenés transacciones que corren operaciones lentas, el timeout default puede ser diferente. Verificá y setealo explícitamente:

// ✅ Timeout explícito — no dependas del default
await prisma.$transaction(
  async (tx) => {
    // operaciones de la transacción
  },
  {
    maxWait: 5000,  // ms — máximo tiempo esperando adquirir la transacción
    timeout: 10000  // ms — máximo tiempo de ejecución
  }
)
Enter fullscreen mode Exit fullscreen mode

Checklist de migración Prisma 5 → 6

Este es el orden que sigo para hacer un upgrade sin sustos. No es el único camino, pero cubre los edge cases que más aparecen:

Antes del upgrade:

  • [ ] Revisá todas las previewFeatures en el schema — verificá cuáles pasaron a GA en v6 y eliminá las flags correspondientes
  • [ ] Auditá todos los where que reciben variables opcionales — reemplazá el patrón campo: variable | undefined por construcción condicional explícita
  • [ ] Buscá imports desde rutas internas de @prisma/client — centralizá en el punto de entrada público
  • [ ] Verificá los timeouts explícitos en todas las $transaction interactivas
  • [ ] Corré prisma migrate status y asegurate de que no haya migraciones pendientes antes del upgrade

Durante el upgrade:

  • [ ] Actualizá prisma y @prisma/client a la misma versión mayor simultáneamente
  • [ ] Corré prisma generate y revisá el output completo — no solo que termine sin error
  • [ ] Corré el test suite de integración (si tenés) apuntando a una DB de staging, no producción
  • [ ] Si usás Next.js 16 con Edge Runtime, verificá la configuración del cliente de edge según la doc de Prisma 6

Después del upgrade:

  • [ ] Monitoreá los query logs en las primeras horas — buscá queries más lentas o resultados inesperados en relaciones opcionales
  • [ ] Verificá que el singleton de PrismaClient siga funcionando correctamente en el ciclo de vida de Next.js (hot reload en dev, instancia única en prod)

FAQ — Prisma 6 migration breaking changes

¿Prisma 6 es compatible con Prisma 5 sin cambios?

No completamente. Hay breaking changes documentados en la guía oficial. La mayoría son manejables, pero requieren revisión manual — especialmente en schemas con previewFeatures, queries relacionales con valores opcionales y uso del cliente de edge. No es un upgrade de patch; tomalo en serio.

¿El schema de Prisma (schema.prisma) cambia entre v5 y v6?

El formato del schema no cambió drásticamente, pero sí hay flags de previewFeatures que deben removerse porque pasaron a GA. Si las dejás, el CLI puede emitir warnings o errores dependiendo de la versión puntual. Revisá la lista completa en el anuncio oficial.

¿Mis queries con include y relaciones opcionales van a funcionar igual?

Probablemente sí para los casos simples. El riesgo está en queries que pasan undefined condicionalmente a campos opcionales del where. Si construís los filtros de forma explícita (sin depender del comportamiento de undefined), el riesgo es bajo.

¿Prisma 6 funciona con Next.js 16 App Router y Server Actions?

Sí. El stack Next.js 16 + Server Actions + Prisma 6 + PostgreSQL funciona bien. Lo que requiere atención es la instancia del cliente (singleton global) y la configuración del Edge Runtime si la usás. Los patterns de Prisma con Server Actions que cubrí en el post anterior sobre Server Actions y Prisma siguen siendo válidos — solo verificá el punto de entrada del cliente.

¿Puedo hacer el upgrade en producción directamente?

Mi recomendación es no. El flujo más seguro es: rama de upgrade → staging con DB similar a producción → test suite de integración → deploy en horario de bajo tráfico. El upgrade en sí no es riesgoso si seguís el checklist, pero la validación previa es lo que te salva de sorpresas.

¿$queryRawTyped reemplaza a $queryRaw?

No lo reemplaza, lo complementa. $queryRawTyped es la nueva API para SQL type-safe con inferencia de tipos — es una mejora genuina para queries SQL complejas que el ORM no puede expresar bien. $queryRaw sigue funcionando. Si querés explorar la nueva API, el anuncio oficial tiene los ejemplos; si ya usás query logging con PostgreSQL para debuguear queries pesadas, $queryRawTyped va a ser tu aliado natural.


Conclusión: vale el upgrade, pero hay que ganárselo

Prisma 6 es un paso adelante real — mejor performance, client generation más rápida y SQL type-safe son mejoras concretas que se sienten en proyectos con schemas complejos. No lo estoy cuestionando.

Lo que sí estoy diciendo es que hay tres comportamientos que no gritan en el compilador: la limpieza de previewFeatures, el manejo de undefined en where condicionales y los imports del cliente generado. Si los ignorás, te enterás en runtime.

Lo incómodo de verdad es que ninguno de los tres es un bug de Prisma — son decisiones razonables del equipo que priorizan comportamiento correcto sobre compatibilidad silenciosa. Pero si no leés la guía de migración completa antes de correr npm install prisma@6, el costo lo pagás vos.

Mi recomendación práctica: antes de hacer el upgrade, corré un grep en la codebase por previewFeatures, por patrones campo: variable en objetos where, y por imports desde rutas internas de @prisma/client. Si los tres resultados están limpios, el upgrade va a ser tranquilo. Si aparece algo, lo sabés antes de empezar.

Si estás en el camino de hardening de queries y logging, el post sobre Prisma query logging y PostgreSQL tiene contexto útil para el lado del monitoreo post-upgrade. Y si el proyecto usa TypeScript strict mode, las opciones strictNullChecks y noUncheckedIndexedAccess van a hacer más visibles exactamente los patrones de undefined que describí acá.


Fuente original:


Este artículo fue publicado originalmente en juanchi.dev

Top comments (0)