🎯 Prisma ORM — Complete Cheatsheet 🎯
Prisma ORM es un toolkit de base de datos de próxima generación para Node.js y TypeScript que adopta un enfoque schema-first: un único archivo schema.prisma define el modelo de datos, las relaciones, las validaciones y la fuente de datos, y a partir de él se genera automáticamente un cliente tipado (PrismaClient). Esto elimina el mapeo manual objeto-relacional, garantiza type safety end-to-end y acelera drásticamente el desarrollo. Este cheatsheet cubre desde el modelado de esquemas y relaciones hasta consultas anidadas, transacciones, migraciones, raw SQL, extensión del cliente, middlewares, performance, Prisma Accelerate y patrones de producción. Ideal para desarrolladores backend que migran desde TypeORM, Sequelize, [Mongoose](/base_datos/nosql/mongoose “Mongoose es una biblioteca ODM (Object-Document Mapping) para MongoDB y Node.js que proporciona un esquema basado en validación, modelado de relaciones, middleware pre/post, transformación de consultas y herramientas de tipado estricto con TypeScript.”) o SQL directo y buscan un ORM moderno, tipado y con excelente DX.
1. 🌟 Conceptos Fundamentales
- Schema-first: Toda la estructura de la BD se declara en
prisma/schema.prisma. Es la única fuente de verdad; no hay decoradores en clases ni migraciones manuales. - Cliente autogenerado:
prisma generateproduce@prisma/clientcon tipos TypeScript derivados del schema. Autocompletado, validación de tipos y detección temprana de errores.- Por qué importa: cambias el schema → regeneras → toda tu app queda tipada automáticamente.
- Type Safety End-to-End: Desde la query hasta el resultado, todo está tipado.
findUniquesobre un campo@uniqueretornaT | null,findFirstretornaT | null,findManyretornaT[]. - Migraciones declarativas:
prisma migrate devgenera SQL de migración a partir del diff del schema. Versionadas en git, reproducibles, aplicables en CI. - Query Builder fluido: API encadenable y composable (
prisma.user.findMany({ where: {...}, include: {...} })). No es SQL stringificado, es código tipado. - Nested Writes / Reads: Crear, actualizar o leer relaciones en una sola operación atómica. Elimina queries manuales para joins o inserts relacionados.
- Transacciones ACID nativas:
prisma.$transaction([...])oprisma.$transaction(async (tx) => {...})para operaciones multi-escritura. - Multi-database: Soporta PostgreSQL, MySQL, MariaDB, SQL Server, SQLite, CockroachDB y MongoDB. Un mismo schema con
providercambiado. - Extensiones del cliente:
prisma.$extends()permite añadir métodos custom, middlewares y lógica transversal sin tocar el schema.
2.
Instalación y Configuración Inicial
-
Instalación en proyecto existente:
npm install prisma --save-dev npm install @prisma/client npx prisma init # Crea prisma/schema.prisma y .env con DATABASE_URL -
Estructura resultante:
project/ ├── prisma/ │ ├── schema.prisma # Modelo de datos + datasource + generator │ └── migrations/ # SQL versionado generado por migrate ├── src/ └── .env # DATABASE_URL="postgresql://..." -
schema.prismamínimo:datasource db { provider = "postgresql" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } model User { id Int @id @default(autoincrement()) email String @unique name String? } -
Comandos esenciales del CLI:
Comando Función npx prisma initInicializa proyecto Prisma npx prisma generateGenera PrismaClient desde schema npx prisma migrate devCrea y aplica migración en dev npx prisma migrate deployAplica migraciones pendientes en prod npx prisma migrate resetResetea DB + reaplica migraciones + seeds npx prisma db pushSincroniza schema sin generar migración (dev rápido) npx prisma db seedEjecuta script de seed npx prisma studioAbre UI visual para explorar/editar datos npx prisma validateValida sintaxis del schema npx prisma formatFormatea schema.prisma -
Cliente singleton (recomendado):
// src/lib/prisma.ts import { PrismaClient } from '@prisma/client' const globalForPrisma = globalThis as unknown as { prisma: PrismaClient } 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
3. 📝 Modelado Básico y Tipos
3.1. Definición de Modelos
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
age Int?
isActive Boolean @default(true)
balance Decimal @db.Decimal(10, 2)
role Role @default(USER)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
posts Post[]
@@index([email])
@@map("users") // nombre de tabla en DB
}
enum Role {
USER
ADMIN
MODERATOR
}
3.2. Tipos Escalares y Atributos
- Escalares:
String,Int,BigInt,Float,Decimal,Boolean,DateTime,Json,Bytes. - Atributos de campo:
Atributo Efecto @idClave primaria @uniqueÍndice único @default(value)Valor por defecto ( autoincrement(),now(),uuid(),cuid())@updatedAtActualiza automáticamente a now()en cada cambio@map("db_column")Renombra en la BD @db.VarChar(255)Tipo nativo específico del provider @ignoreIgnorado por Prisma (útil en legado) - Atributos de modelo:
Atributo Efecto @@id([a, b])Clave primaria compuesta @@unique([a, b])Índice único compuesto @@index([a, b])Índice normal @@index([email(sort: Desc)])Índice con orden @@map("table_name")Renombra la tabla @@schema("custom")Schema/namespace (Postgres)
3.3. Claves Compuestas y Tipos Nativos
model PostView {
postId Int
userId Int
viewedAt DateTime
post Post @relation(fields: [postId], references: [id])
user User @relation(fields: [userId], references: [id])
@@id([postId, userId, viewedAt]) // PK compuesta
}
// Tipos nativos Postgres
model Product {
id Int @id @default(autoincrement())
price Decimal @db.Decimal(10, 2)
tags String[] // array nativo en Postgres
metadata Json @db.JsonB
location Unsupported("geometry") // PostGIS
}
4. 🔗 Relaciones (1-1, 1-n, m-n)
4.1. Uno a Muchos (1-n)
model User {
id Int @id @default(autoincrement())
email String @unique
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
authorId Int
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
}
authorIdes la foreign key en Post.onDelete: Cascadeelimina posts cuando se borra el user. Opciones:Cascade,SetNull,Restrict,NoAction,SetDefault.
4.2. Uno a Uno (1-1)
model User {
id Int @id @default(autoincrement())
pro
Pro
?
}
model Pro
{
id Int @id @default(autoincrement())
bio String
userId Int @unique // ← la unicidad en el FK convierte en 1-1
user User @relation(fields: [userId], references: [id])
}
4.3. Muchos a Muchos (m-n)
// Implícita (Prisma gestiona la tabla intermedia)
model Post {
id Int @id @default(autoincrement())
tags Tag[]
}
model Tag {
id Int @id @default(autoincrement())
name String @unique
posts Post[]
}
// Crea automáticamente tabla "_PostToTag"
// Explícita (cuando necesitas campos extra en la relación)
model Post {
id Int @id @default(autoincrement())
categories PostCategory[]
}
model Category {
id Int @id @default(autoincrement())
name String @unique
posts PostCategory[]
}
model PostCategory {
postId Int
categoryId Int
addedAt DateTime @default(now())
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade)
@@id([postId, categoryId])
}
4.4. Relaciones Auto-referenciales
model Employee {
id Int @id @default(autoincrement())
name String
managerId Int?
manager Employee? @relation("ManagerReports", fields: [managerId], references: [id])
reports Employee[] @relation("ManagerReports")
}
4.5. Relaciones con Nombre Disambiguado
model User {
id Int @id @default(autoincrement())
sentMessages Message[] @relation("SentMessages")
receivedMessages Message[] @relation("ReceivedMessages")
}
model Message {
id Int @id @default(autoincrement())
senderId Int
receiverId Int
sender User @relation("SentMessages", fields: [senderId], references: [id])
receiver User @relation("ReceivedMessages", fields: [receiverId], references: [id])
}
5.
Lecturas: findUnique, findFirst, findMany
// findUnique: por campo @unique o @id. Retorna T | null
const user = await prisma.user.findUnique({
where: { email: 'ana@dev.com' },
include: { posts: true },
})
// findUniqueOrThrow: lanza error si no existe
const user = await prisma.user.findUniqueOrThrow({
where: { id: 1 },
})
// findFirst: primer match (con orden, filtros complejos)
const admin = await prisma.user.findFirst({
where: { role: 'ADMIN' },
orderBy: { createdAt: 'desc' },
})
// findMany: todos los matches
const users = await prisma.user.findMany({
where: { isActive: true },
skip: 20,
take: 10,
orderBy: [{ createdAt: 'desc' }, { name: 'asc' }],
select: { id: true, email: true, name: true },
})
6. ✍️ Creación: create y createMany
// Create simple
const user = await prisma.user.create({
data: { email: 'ana@dev.com', name: 'Ana' },
})
// Create con relación anidada
const user = await prisma.user.create({
data: {
email: 'ana@dev.com',
name: 'Ana',
posts: {
create: [
{ title: 'Post 1', content: '...' },
{ title: 'Post 2', content: '...' },
],
},
pro
: {
create: { bio: 'Dev' },
},
},
include: { posts: true, pro
: true },
})
// Connect a registro existente
const post = await prisma.post.create({
data: {
title: 'Nuevo',
author: { connect: { id: 1 } }, // vincula al user id=1
},
})
// createMany: inserción masiva eficiente (no retorna registros)
const result = await prisma.user.createMany({
data: [
{ email: 'a@x.com', name: 'A' },
{ email: 'b@x.com', name: 'B' },
{ email: 'c@x.com', name: 'C' },
],
skipDuplicates: true, // ignora duplicados en campos únicos
})
// result.count = 3
7. 🔄 Actualización: update, updateMany, upsert
// update simple
await prisma.user.update({
where: { id: 1 },
data: { name: 'Ana García', age: { increment: 1 } },
})
// Operadores numéricos en update
await prisma.product.update({
where: { id: 1 },
data: {
stock: { decrement: 5 },
price: { multiply: 1.1 },
views: { increment: 1 },
},
})
// upsert: update si existe, create si no
const user = await prisma.user.upsert({
where: { email: 'ana@dev.com' },
update: { name: 'Ana G.' },
create: { email: 'ana@dev.com', name: 'Ana G.', role: 'USER' },
})
// updateMany (no retorna registros, solo cuenta)
const result = await prisma.user.updateMany({
where: { role: 'GUEST', createdAt: { lt: oneMonthAgo } },
data: { isActive: false },
})
// result.count = 42
// Actualizar relaciones
await prisma.post.update({
where: { id: 1 },
data: {
author: { connect: { id: 5 } }, // cambiar autor
tags: {
connect: [{ id: 10 }, { id: 11 }], // añadir tags
disconnect: [{ id: 3 }], // quitar tag
set: [{ id: 10 }, { id: 12 }], // reemplazar todos
},
},
})
8. 🗑️ Eliminación: delete, deleteMany
// delete simple
await prisma.user.delete({ where: { id: 1 } })
// delete con relación anidada (soft delete o limpieza)
await prisma.user.update({
where: { id: 1 },
data: {
posts: { deleteMany: { published: false } }, // elimina posts no publicados
},
})
// deleteMany
await prisma.session.deleteMany({
where: { expiresAt: { lt: new Date() } },
})
// Soft delete con campo deletedAt
await prisma.user.update({
where: { id: 1 },
data: { deletedAt: new Date() },
})
- Cascadas: si el schema define
onDelete: Cascade, eliminar unUserelimina automáticamente susPostrelacionados.
9. 🎯 Filtros y Operadores (Where Clauses)
// Comparación básica
prisma.user.findMany({ where: { age: { gte: 18, lte: 65 } } })
// Lógico
prisma.user.findMany({
where: {
AND: [
{ isActive: true },
{ OR: [{ role: 'ADMIN' }, { age: { gt: 50 } }] },
],
NOT: { email: { contains: 'spam' } },
},
})
// Strings
prisma.user.findMany({
where: {
name: { contains: 'ana', mode: 'insensitive' }, // ILIKE
email: { startsWith: 'admin', endsWith: '@dev.com' },
},
})
// Nullability
prisma.user.findMany({ where: { phone: null } })
prisma.user.findMany({ where: { phone: { not: null } } })
// Arrays (Postgres)
prisma.post.findMany({
where: {
tags: { has: 'typescript' }, // contiene elemento
tags: { hasEvery: ['typescript', 'prisma'] },
tags: { hasSome: ['react', 'vue'] },
tags: { isEmpty: false },
},
})
// Relaciones
prisma.user.findMany({
where: {
posts: { some: { published: true } }, // tiene al menos un post publicado
posts: { every: { published: true } },// todos sus posts publicados
posts: { none: { published: false } },// ningún post sin publicar
},
})
10. 📦 include, select y Proyecciones
// include: trae relación completa
const user = await prisma.user.findUnique({
where: { id: 1 },
include: {
posts: true,
pro
: true,
},
})
// include anidado con filtros
const user = await prisma.user.findUnique({
where: { id: 1 },
include: {
posts: {
where: { published: true },
orderBy: { createdAt: 'desc' },
take: 5,
include: { tags: true },
},
},
})
// select: proyección precisa (solo campos pedidos)
const user = await prisma.user.findUnique({
where: { id: 1 },
select: {
id: true,
email: true,
posts: {
select: { id: true, title: true },
},
},
})
// Mezcla select + include anidado con _count
const user = await prisma.user.findUnique({
where: { id: 1 },
select: {
id: true,
email: true,
_count: { select: { posts: true, comments: true } },
latestPost: { select: { id: true, title: true } },
},
})
- Regla:
selectyincludeson mutuamente excluyentes a nivel del mismo objeto. Usaselectpara máxima eficiencia.
11. 📊 Agregaciones, Agrupación y Conteos
// Conteos
const total = await prisma.user.count({ where: { isActive: true } })
// Agregaciones globales
const stats = await prisma.order.aggregate({
where: { status: 'PAID' },
_count: true,
_sum: { amount: true },
_avg: { amount: true },
_min: { amount: true, createdAt: true },
_max: { amount: true },
})
// stats._sum.amount, stats._avg.amount, etc.
// GroupBy (GROUP BY SQL)
const byMonth = await prisma.order.groupBy({
by: ['status'],
where: { createdAt: { gte: new Date('2024-01-01') } },
_count: { _all: true },
_sum: { amount: true },
_avg: { amount: true },
having: {
amount: { _sum: { gt: 1000 } }, // HAVING
},
orderBy: { _sum: { amount: 'desc' } },
})
// Conteos por relación sin cargar datos
const users = await prisma.user.findMany({
select: {
id: true,
name: true,
_count: { select: { posts: true, comments: true } },
},
})
// users[0]._count.posts = 12
12. 🔀 Transacciones
// Transacción interactiva (recomendada para lógica compleja)
const result = await prisma.$transaction(async (tx) => {
const from = await tx.account.findUniqueOrThrow({ where: { id: 'A' } })
if (from.balance < 100) throw new Error('Saldo insuficiente')
await tx.account.update({
where: { id: 'A' },
data: { balance: { decrement: 100 } },
})
await tx.account.update({
where: { id: 'B' },
data: { balance: { increment: 100 } },
})
return await tx.transfer.create({
data: { fromId: 'A', toId: 'B', amount: 100 },
})
})
// Transacción por lotes (array de operaciones, ejecución secuencial)
const [user, post, tag] = await prisma.$transaction([
prisma.user.create({ data: { email: 'a@x.com' } }),
prisma.post.create({ data: { title: 'Hola' } }),
prisma.tag.upsert({
where: { name: 'new' },
create: { name: 'new' },
update: {},
}),
])
// Opciones avanzadas
await prisma.$transaction(
async (tx) => { /* ... */ },
{
maxWait: 5000, // ms esperando transacción disponible
timeout: 10000, // ms antes de abortar
isolationLevel: 'Serializable', // ReadUncommitted, ReadCommitted, RepeatableRead, Serializable
}
)
13. 🚧 Migraciones con prisma migrate
- Flujo dev (crear/aplicar migraciones):
npx prisma migrate dev --name add_posts_table # 1. Detecta cambios en schema # 2. Genera SQL en prisma/migrations/YYYYMMDDHHMMSS_add_posts_table/migration.sql # 3. Aplica a la BD # 4. Regenera PrismaClient automáticamente - Flujo prod (aplicar migraciones existentes):
# CI/CD o docker entrypoint npx prisma migrate deploy # Solo aplica migraciones pendientes. No genera ni resetea. - Reset completo (solo dev):
npx prisma migrate reset # Drop DB + reaplica todas + corre seeds - Sincronización rápida sin migraciones:
npx prisma db push # Útil en prototipado. NO genera archivo de migración. - Migraciones SQL manuales:
npx prisma migrate dev --create-only # Solo crea el archivo SQL vacío para editar manualmente # Luego: npx prisma migrate dev para aplicar - Marcar migración como aplicada (base histórica):
npx prisma migrate resolve --applied 20240101000000_init
14.
Raw SQL y Consultas Avanzadas
// Query raw (retorna resultados tipados si se usa $queryRawTyped)
import { Prisma, PrismaPromise } from '@prisma/client'
const users = await prisma.$queryRaw<User[]>`
SELECT id, email, name
FROM users
WHERE age > ${minAge}
AND status = ${'active'}
`
// Ejecutar sin retorno
const result = await prisma.$executeRaw`
UPDATE users SET "loginCount" = "loginCount" + 1 WHERE id = ${userId}
`
// queryRawUnsafe (cuando NO hay template literals disponibles)
const rows = await prisma.$queryRawUnsafe(
'SELECT * FROM users WHERE email = $1',
email, // parámetros parametrizados, seguros contra SQL injection
)
// Tipado estricto con $queryRawTyped (generado desde schema)
import { topUsers } from '@prisma/client/sql'
const top = await prisma.$queryRawTyped(topUsers(10))
- Regla: Siempre usar template literals con
$queryRaw(nunca concatenar strings). Prisma parametriza automáticamente. Prisma.sqlpara construir queries dinámicas:import { Prisma } from '@prisma/client' const conditions: Prisma.Sql[] = [] if (filters.email) conditions.push(Prisma.sql`email = ${filters.email}`) if (filters.age) conditions.push(Prisma.sql`age > ${filters.age}`) const where = conditions.length ? Prisma.sql`WHERE ${Prisma.join(conditions, ' AND ')}` : Prisma.empty const users = await prisma.$queryRaw`SELECT * FROM users ${where}`
15. 🌱 Seeds
- Configurar en
package.json:{ "prisma": { "seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts" } } prisma/seed.ts:import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() async function main() { const admin = await prisma.user.upsert({ where: { email: 'admin@dev.com' }, update: {}, create: { email: 'admin@dev.com', name: 'Admin', role: 'ADMIN', posts: { create: [ { title: 'Bienvenida', content: 'Primer post' }, { title: 'Tutorial', content: 'Segundo post' }, ], }, }, }) console.log('Seed complete:', admin) } main() .catch((e) => { console.error(e); process.exit(1) }) .finally(async () => { await prisma.$disconnect() })- Ejecutar:
npx prisma db seed(también corre trasmigrate reset).
16. 🧩 Extensiones del Cliente, Middlewares y Logging
16.1. Client Extensions ($extends)
// Añadir métodos custom
const xprisma = prisma.$extends({
model: {
user: {
async findActive() {
const ctx = Prisma.getExtensionContext(this)
return ctx.findMany({ where: { isActive: true } })
},
},
},
result: {
user: {
fullName: {
needs: { firstName: true, lastName: true },
compute(user) { return `${user.firstName} ${user.lastName}` },
},
},
},
query: {
user: {
async findMany({ args, query }) {
// Soft delete: filtrar automáticamente
args.where = { ...args.where, deletedAt: null }
return query(args)
},
},
},
})
// Uso:
const active = await xprisma.user.findActive()
const u = await xprisma.user.findUnique({ where: { id: 1 } })
u?.fullName // computado automáticamente
16.2. Logging
const prisma = new PrismaClient({
log: [
{ level: 'query', emit: 'event' },
{ level: 'warn', emit: 'stdout' },
{ level: 'error', emit: 'stdout' },
],
})
prisma.$on('query', (e) => {
console.log(`Query: ${e.query}`)
console.log(`Params: ${e.params}`)
console.log(`Duration: ${e.duration}ms`)
})
17. ⚡ Rendimiento y Optimización
selectsobreinclude: Solo trae los campos necesarios. Reduce payload y parsing.- Evita
includeprofundo en listas:findMany({ include: { posts: { include: { comments: true } } } })dispara múltiples queries. Usaselectcon proyecciones específicas. - Índices en el schema:
@@index([email]),@@index([userId, createdAt]). Prisma genera el índice al migrar. cursor-based pagination (mejor queskip/takeen datasets grandes):const page = await prisma.post.findMany({ take: 20, cursor: { id: lastSeenId }, orderBy: { id: 'asc' }, })skip/takepara offset pagination:const { data, total } = await prisma.$transaction([ prisma.post.findMany({ skip: 20, take: 10, orderBy: { createdAt: 'desc' } }), prisma.post.count(), ])- Connection pooling: Prisma usa el pool del driver. En serverless, usa singleton + Prisma Accelerate.
prisma.$queryRawpara queries complejas: Un JOIN complejo o una subquery anidada puede ser más eficiente en SQL directo.- Batch reads: En lugar de N queries individuales, usa
findMany({ where: { id: { in: ids } } }). prisma.$transaction([...])para múltiples writes: reduce round-trips.
18. ☁️ Prisma Accelerate, Studio y Data Proxy
- Prisma Accelerate (cache global + pooling serverless):
import { withAccelerate } from '@prisma/extension-accelerate' const prisma = new PrismaClient().$extends(withAccelerate()) // Query cacheada por 60s await prisma.product.findMany({ cacheStrategy: { ttl: 60 } }) // Invalidación explícita await prisma.product.findMany({ cacheStrategy: { swr: 30, ttl: 60 } }) - Prisma Studio:
npx prisma studio→ UI web para CRUD visual. Útil para debugging y exploración. - Prisma Data Proxy: URL especial tipo
prisma://aws-us-east-1.prisma-data.net/?api_key=...que enruta queries a través de Prisma Cloud (pooling, caching, observabilidad).
19. ⚠️ Errores Comunes y Trampas
- Crear múltiples instancias de
PrismaClient: En dev con hot-reload o en serverless, cada instancia agota conexiones.- Fix: Singleton con
globalThis(ver sección 2).
- Fix: Singleton con
- Olvidar
npx prisma generatetras cambiar schema: El cliente se queda con tipos viejos y TypeScript no detecta errores.- Fix: Integrar
prisma generateenpostinstally en CI.
- Fix: Integrar
includevsselectjuntos al mismo nivel: Prisma lanza error.- Fix: Usa uno u otro. Para select + relaciones anidadas, usa
selectconincludesolo dentro.
- Fix: Usa uno u otro. Para select + relaciones anidadas, usa
findUniquecon campo no único: Error de compilación/TypeScript.- Fix: Solo usa
findUniquesobre campos@ido@unique. Para el resto,findFirst.
- Fix: Solo usa
updateMany/deleteManysinwhere: Afecta toda la tabla sin confirmación.- Fix: Siempre pasar
whereexplícito. En scripts de limpieza, valida primero concount().
- Fix: Siempre pasar
- N+1 en bucles:
for (const userId of ids) { await prisma.user.findUnique(...) }.- Fix:
prisma.user.findMany({ where: { id: { in: ids } } })+ mapeo manual.
- Fix:
- Transacciones en bucle:
await prisma.$transaction(...)dentro deforEachanula el beneficio.- Fix: Construye array de operaciones y pásalas a
$transaction([...]).
- Fix: Construye array de operaciones y pásalas a
- Migraciones rotas por cambios manuales en DB: Prisma pierde sincronía con
_prisma_migrations.- Fix: Nunca modifiques DB fuera de migraciones. Usa
prisma migrate resolvepara reparar.
- Fix: Nunca modifiques DB fuera de migraciones. Usa
- Enum en JS no coincide con DB: Prisma genera enums TS que pueden no matchear valores legacy.
- Fix: Usa
@@mapen enum values o normaliza en seed.
- Fix: Usa
- Falta de índices en campos de
wherefrecuentes:findManycon filtros sin índice → sequential scan.- Fix:
@@index([campo])en schema +prisma migrate dev.
- Fix:
- Soft delete sin middleware global: Cada query debe añadir
deletedAt: nullmanualmente.- Fix: Usa Client Extension con
queryhook (ver sección 16).
- Fix: Usa Client Extension con
Jsontype sin validación:metadata: Jsonacepta cualquier cosa. Errores en runtime.- Fix: Valida con Zod en capa de aplicación antes de persistir.
- Serverless cold starts con Prisma: Primera query tarda 200-500ms.
- Fix: Usa Prisma Accelerate o Data Proxy para connection pooling global.
- Cambiar
providersin migrar: Cambiar de Postgres a MySQL requiere reescribir schema y migraciones.- Fix: Decidir provider al inicio. Si migras, planifica como proyecto nuevo.
20.
Mejores Prácticas y Consejos de Experto
- Schema como fuente de verdad única: Nunca modifiques la BD directamente. Todo pasa por
schema.prisma+migrate. - Separa DTOs de modelos Prisma: Expón
UserDTO(público) noUser(interno). Evita fugas de campos sensibles (password,deletedAt). - Usa
selecten APIs públicas: Proyecta solo campos necesarios. Reduce payload, evita sobre-exposición. - Soft delete con extensiones: Un
deletedAt: DateTime?+ Client Extension que filtre automáticamente = patrón limpio. - Valida input con Zod antes de Prisma:
const data = schema.parse(body); await prisma.user.create({ data }). Doble validación: schema en DB, Zod en entrada. - Nombra índices con claridad:
@@index([userId, createdAt], map: "idx_posts_user_created")facilita debugging y análisis deEXPLAIN. updatedAt: DateTime @updatedAten todos los modelos auditables: tracking automático sin código extra.- Usa
createManypara bulk inserts: 10-100x más rápido quecreateen bucle. Ideal para seeds, ETL, importaciones. - Perfila queries en dev:
log: ['query']+prisma.$on('query', ...)o integra con OpenTelemetry (@prisma/instrumentation). - Pagina por cursor en feeds infinitos: Más eficiente que offset y estable ante inserciones.
- Centraliza lógica de negocio en services:
prismaenrepositories/, lógica enservices/, controllers delgados. Facilita testing y reutilización. - Testea con
@prisma/clientmock o DB en memoria:- Opción 1:
jest-mock-extendedpara mockearPrismaClient. - Opción 2: SQLite en memoria (
url = ") para tests de integración reales.
:./test.db"
- Opción 1:
- Migraciones en CI:
prisma migrate deployen pipeline. Si falla, no despliegues. - Versiona
migrations/en git: Es el histórico oficial de cambios de schema. Nunca ignores. - Usa
prisma formaten pre-commit: Mantiene el schema consistente entre colaboradores. - Evita enums para flags binarios:
isActive: Booleanes más claro y eficiente questatus: Status { ACTIVE, INACTIVE }. - Relaciones m-n explícitas cuando necesites metadatos:
PostCategoryconaddedAt,addedBy, etc. _counten lugar deinclude+length:select: { _count: { select: { posts: true } } }es O(1) vs O(N).- Preview features con cuidado:
previewFeatures = ["postgresqlExtensions"]pueden cambiar entre versiones. Fija versión de Prisma. - Documenta el schema con
///comments:
Los comentarios aparecen en el PrismaClient tipado./// User account with email-based authentication model User { /// Unique email used for login email String @unique } - Monitorea con Prisma Pulse + Accelerate: Change streams + caché global = patrón moderno para apps real-time.
Este cheatsheet proporciona una referencia completa para Prisma ORM, cubriendo modelado schema-first con TypeScript, relaciones (1-1, 1-n, m-n), operaciones CRUD tipadas, filtros avanzados, includes y selects, agregaciones, transacciones ACID, migraciones, raw SQL, seeds, extensiones del cliente, optimización de rendimiento, Prisma Accelerate y patrones de producción, junto con las mejores prácticas para construir aplicaciones Node.js/TypeScript escalables, seguras y de alto rendimiento sobre múltiples bases de datos en entornos reales.