Saltar al contenido

Prisma o Drizzle: la elección va de dónde vive el esquema

Uno genera un cliente a partir de un fichero de esquema que le pertenece; el otro es SQL tipado que escribe usted. La decisión va menos de sintaxis que de quién es dueño de su base de datos.

6 min de lectura

Los dos son buenos. Hay equipos que entregan aplicaciones serias con cada uno, y la discusión entre ellos en internet es más ruidosa que la diferencia en el resultado.

Lo que difiere de verdad es una pregunta que debería estar contestando de todas formas: ¿es el esquema un fichero en su repositorio a partir del cual se genera la base de datos, o es la base de datos la verdad y su código una vista tipada sobre ella?

Prisma: el fichero de esquema es la fuente de verdad

Escribe schema.prisma, y un cliente generado le da una API tipada.

model Order {
  id        String   @id @default(cuid())
  total     Int
  customer  Customer @relation(fields: [customerId], references: [id])
  customerId String
  createdAt DateTime @default(now())
}
const orders = await db.order.findMany({
  where: { total: { gt: 5000 } },
  include: { customer: true },
  take: 20,
});

En qué es bueno. Un fichero describe el modelo entero, y las relaciones están explícitas en él. El cliente es agradable, explorable y difícil de usar mal. Las migraciones se generan a partir de la diferencia entre el esquema y la base de datos, lo que elimina una categoría entera de error escrito a mano. Para un equipo que no domina SQL, es una diferencia grande de productividad y es la razón honesta de su popularidad.

Dónde le cuesta. El cliente generado es un artefacto real: hay que generarlo en la compilación, y no es pequeño, lo que se nota en un bundle serverless y en el arranque en frío. Y la abstracción es lo bastante completa como para que bajar por debajo de ella, cuando necesita una consulta que la API no expresa, se sienta como abandonar la herramienta en lugar de usarla.

Drizzle: el esquema es TypeScript y las consultas son SQL

Describe tablas en TypeScript, y el constructor de consultas está lo bastante cerca de SQL como para que leerlo le enseñe la consulta.

export const orders = pgTable('orders', {
  id: text('id').primaryKey(),
  total: integer('total').notNull(),
  customerId: text('customer_id').notNull().references(() => customers.id),
  createdAt: timestamp('created_at').defaultNow(),
});
 
const rows = await db
  .select()
  .from(orders)
  .where(gt(orders.total, 5000))
  .limit(20);

En qué es bueno. Es delgado. No hay cliente generado ni motor en ejecución, así que el bundle es pequeño y el arranque en frío mejor, que es el argumento que importa en una plataforma donde cada función carga sus dependencias. Y como el constructor de consultas mapea sobre SQL, una consulta compleja se escribe en la misma herramienta que una simple en lugar de en una vía de escape.

Dónde le cuesta. Tiene que saber SQL, y la ergonomía de las relaciones es más trabajo que include. La generación de migraciones existe y es menos opinada. Para un equipo que prefiere pensar en objetos antes que en joins, es más fricción por consulta.

La pregunta que lo decide

No la sintaxis. Pregunte de quién es el esquema.

Si es de la aplicación - proyecto nuevo, un equipo, la base de datos existe para servir a este código - un fichero de esquema que genera la base de datos es una disposición limpia y el modelo de Prisma encaja exactamente.

Si es de la base de datos - es anterior a la aplicación, otros sistemas escriben en ella, un DBA tiene opiniones, hay vistas y triggers y cosas que su ORM no creó - entonces una herramienta que describe lo que hay encaja mejor que una que quiere definirlo.

El segundo caso es más común de lo que sugieren los textos sobre proyectos nuevos, y es donde un ORM centrado en el esquema empieza a sentirse como si peleara con usted.

La parte específica de Next.js

Aquí importan dos cosas que no importarían en un servidor de larga vida.

Bundle y arranque en frío. Un cliente más pesado en cada instancia de función se paga en cada arranque en frío. Es un punto genuino a favor de la opción más delgada, y es menor que una sola consulta mal formada. Mida su propia ruta antes de tratarlo como decisivo.

Dónde se construye el cliente. Idéntico para los dos y más importante que la elección: una instancia por ámbito de módulo, no una por petición. Equivóquese en eso con cualquiera de los dos y estará leyendo sobre límites de conexiones en lugar de sobre ORM.

Si despliega al runtime de edge, revise el camino del driver y no el ORM: los dos funcionan allí con un driver capaz de HTTP, y los dos no funcionan con uno TCP.

Qué sale mal de verdad, con cualquiera de los dos

Nunca es el ORM. En todas las bases de código que nos han entregado, los problemas de la capa de datos eran la misma lista corta: una consulta dentro de un bucle, una página cargando los mismos datos en tres componentes porque nadie los dedujo, ningún índice detrás de un filtro que controla un usuario, y un cliente construido por petición.

Los cuatro son posibles en las dos herramientas e invisibles en las dos. Elija lo que elija, lo que merece la pena añadir en la primera semana es una forma de ver el número de consultas de una ruta, porque ese número es lo que de verdad va a depurar, y ninguna de las dos elecciones lo cambia.

Volver a todos los artículos