Express.js
ORMs y query builders
Compara ORMs y query builders dentro de aplicaciones Express.js, sus abstracciones, migraciones, rendimiento, transacciones y límites frente al SQL real.
- Última actualización
- Actualizada
- Nivel
- Aplicación
Express.js
Compara ORMs y query builders dentro de aplicaciones Express.js, sus abstracciones, migraciones, rendimiento, transacciones y límites frente al SQL real.
Un ORM o query builder reduce trabajo repetitivo, pero no elimina SQL, transacciones, índices ni el comportamiento real de PostgreSQL.
Estas herramientas traducen operaciones del código a consultas y resultados:
TypeScript
↓ ORM/query builder
SQL
↓
PostgreSQLLa abstracción puede mejorar productividad y tipos, pero introduce su propio modelo, limitaciones y coste de depuración.
Modela entidades, relaciones y operaciones de persistencia. Puede incluir migraciones, identity map, lazy loading o change tracking.
Ejemplos comunes: Prisma, TypeORM, MikroORM, Sequelize.
Construye SQL mediante una API tipada o composable, conservando más visibilidad de la consulta.
Ejemplos: Kysely, Knex, Drizzle en varios de sus usos.
La frontera no siempre es absoluta: algunas herramientas combinan ambos enfoques.
No resuelven automáticamente diseño de dominio, consistencia, permisos ni performance.
const order = await prisma.order.findFirst({
where: {
id: orderId,
tenantId,
},
select: {
id: true,
status: true,
total: true,
},
});El scope por tenant sigue siendo explícito. select evita exponer o cargar columnas innecesarias.
const order = await db
.selectFrom('orders')
.select(['id', 'status', 'total'])
.where('tenant_id', '=', tenantId)
.where('id', '=', orderId)
.executeTakeFirst();La forma se parece más a SQL, lo que facilita razonar sobre joins y filtros.
Un tipo generado describe cómo el código espera la fila. No valida request body ni garantiza que una migración se haya aplicado correctamente en el entorno.
También puede existir drift entre:
Lazy loading puede disparar una query por acceso. Eager loading puede traer demasiados datos.
Inspecciona SQL real:
endpoint lento
→ logs de queries
→ cantidad y duración
→ EXPLAIN ANALYZENo optimices solo mirando el código del ORM.
Una transacción debe utilizar el contexto transaccional proporcionado por la herramienta:
await prisma.$transaction(async (tx) => {
const order = await tx.order.create({ data });
await tx.inventory.updateMany({ ... });
});Pasar el cliente global a un repository dentro del callback puede ejecutar fuera de la transacción. Diseña dependencias para recibir tx o un unit of work.
SQL directo no es fracaso del ORM. Es apropiado para:
Debe seguir parametrizado y probado.
Las migraciones generadas necesitan revisión. Una herramienta puede proponer:
En producción utiliza expand-contract cuando el despliegue no es atómico.
Evitar SQL específico puede facilitar cambiar de motor, pero esa migración rara vez es gratuita. Tipos, índices, aislamiento y queries avanzadas difieren.
No sacrifiques una solución correcta por una portabilidad hipotética sin requisito.
Un repository puede encapsular queries de un módulo, pero evita wrappers que solo repiten la API:
class OrderRepository {
findUnique(args) {
return prisma.order.findUnique(args);
}
}Esto no crea un boundary. Un método útil expresa intención y scope:
findVisibleOrder(actorScope, orderId)Las herramientas exponen códigos propios. Tradúcelos cerca de persistencia o aplicación:
No acoples el contrato HTTP a clases específicas del ORM.
Crear orden:
El ORM facilita operaciones, pero la regla y boundary siguen siendo del caso de uso.
Transacciones desde Express define cómo coordinar cambios atómicos sin mantener recursos abiertos más de lo necesario.