Talently
Talently
Sequelize

Sequelize

El ORM Node.js para bases de datos relacionales

Sequelize es un ORM para Node.js que soporta múltiples bases de datos relacionales como PostgreSQL, MySQL, MariaDB, SQLite y SQL Server. Permite trabajar con bases de datos usando modelos JavaScript u objetos en lugar de SQL directo, gestionando migraciones, relaciones entre modelos, validaciones y transacciones con una API fluida orientada a promesas.

Node.jsJavaScriptTypeScriptSQL

Demanda del mercado

Sequelize es el ORM Node.js más adoptado para bases de datos relacionales, con alta demanda en proyectos Express y NestJS que necesitan persistencia en PostgreSQL o MySQL. Su conocimiento es frecuentemente requerido en posiciones backend Node.js con bases de datos relacionales.

ORM más usado en ecosistema Node.jsAlta demanda en backends Express y NestJSEstándar para SQL en proyectos Node.js

Requisitos técnicos

Intermediate

Requiere dominio de JavaScript o TypeScript, conceptos de bases de datos relacionales y SQL básico. Familiaridad con el patrón Active Record, migraciones de base de datos y conceptos de relaciones como foreign keys y joins es esencial para trabajar eficientemente con Sequelize en proyectos reales.

Casos de uso

Proyectos Reales

Sequelize se utiliza para desarrollar:

  • Capa de persistencia en APIs REST con Express o NestJS
  • Aplicaciones con modelos de datos relacionales complejos
  • Sistemas con múltiples tipos de base de datos en distintos entornos
  • Proyectos que requieren migraciones versionadas del esquema

Tipos de Empresa

Sequelize es adoptado por:

  • Startups con stack Node.js y base de datos PostgreSQL o MySQL
  • Agencias con proyectos Express que necesitan ORM
  • Empresas migrando desde monolitos con ORMs tradicionales a Node.js
  • Equipos fullstack JavaScript que prefieren evitar SQL manual

Escenarios de Producción

Sequelize es ampliamente utilizado en entornos productivos como:

  • APIs con operaciones CRUD complejas sobre múltiples tablas relacionadas
  • Sistemas con transacciones que garantizan consistencia entre operaciones
  • Aplicaciones con esquemas que evolucionan mediante migraciones
  • Backends con lógica de negocio que requiere validaciones a nivel de modelo

Escalabilidad

Sequelize ofrece múltiples mecanismos para escalar aplicaciones:

  • Connection pooling nativo para gestión eficiente de conexiones
  • Scopes para encapsular queries frecuentes y reutilizables
  • Hooks para lógica transversal en el ciclo de vida de los modelos
  • Migraciones para gestionar la evolución del esquema en producción

Ventajas y Desventajas

Ventajas

Soporte para múltiples dialectos de SQL con la misma API.

Sistema de migraciones que versiona los cambios del esquema junto al código.

API de asociaciones que abstrae JOINs complejos en métodos de modelo.

Desventajas

Soporte para TypeScript menos maduro que TypeORM en proyectos tipados.

Las queries complejas pueden volverse verbosas comparadas con SQL directo.

El rendimiento de queries generadas puede ser subóptimo sin revisión del SQL producido.

Comparación

Ventajas de TypeORM

  • TypeScript de primera clase con decoradores
  • Patrón Repository además de Active Record
  • Mejor integración con NestJS por su soporte TypeScript

Consideraciones

TypeORM es preferible en proyectos TypeScript donde el tipado estricto es prioritario. Sequelize es más maduro y tiene mejor soporte para JavaScript puro y proyectos donde TypeScript no es requisito.

Preguntas básicas

Sequelize abstrae el SQL repetitivo para operaciones CRUD, gestiona las relaciones entre modelos, provee un sistema de migraciones para versionar el esquema y añade validaciones a nivel de modelo. Para queries simples y medianas reduce significativamente el código boilerplate y mejora la mantenibilidad.
Sequelize es preferible en proyectos JavaScript puro donde TypeScript no es un requisito, cuando el equipo ya tiene experiencia con Sequelize o cuando se hereda código existente. TypeORM es más adecuado en proyectos TypeScript donde los decoradores y el tipado estricto son prioritarios.
Permite versionar los cambios del esquema de base de datos junto al código fuente en control de versiones. Cada cambio es incremental y reversible, garantizando que todos los entornos tienen el mismo esquema y que los cambios se aplican en orden sin sincronización manual entre developers.
Con métodos como hasOne, hasMany, belongsTo y belongsToMany que Sequelize usa para añadir foreign keys automáticamente y generar los JOINs correctos en las queries. hasMany con belongsTo crea la foreign key en la tabla del modelo hijo y belongsToMany crea una tabla intermedia para relaciones muchos a muchos.
Es cargar las asociaciones junto con el modelo en la misma query usando la opción include en findAll o findOne. Evita el problema N+1 donde Sequelize ejecutaría una query por cada registro para cargar su asociación, combinando los datos en un JOIN eficiente.
Con la API de transacciones que agrupa múltiples operaciones en una unidad atómica. Son necesarias cuando varias operaciones de base de datos deben completarse todas o ninguna, como transferencias de saldo, creación de pedidos con múltiples líneas o cualquier operación donde la inconsistencia parcial sería un problema.
findOne devuelve el primer registro que coincide con la condición o null. findAll devuelve todos los registros que coinciden como array. findOrCreate busca un registro y si no existe lo crea, devolviendo el registro y un booleano indicando si fue creado, siendo útil para operaciones upsert simples.
En proyectos Node.js con bases de datos relacionales donde el equipo prefiere trabajar con modelos JavaScript en lugar de SQL, cuando se necesita portabilidad entre distintos dialectos SQL, o cuando el sistema de migraciones y las validaciones a nivel de modelo aportan valor en un equipo con varios desarrolladores.

Preguntas técnicas

Usando findAll con include para las asociaciones necesarias especificando los modelos a cargar con sus atributos, where para filtros en el modelo principal, y usando el objeto where dentro de include para filtrar por atributos de las asociaciones. Para queries muy complejas se puede usar Sequelize.literal para fragmentos SQL personalizados.
Definiendo scopes en el modelo con addScope o en la definición del modelo con la opción scopes, que son funciones que devuelven opciones de query. Se aplican con Model.scope('nombreScope').findAll() y se pueden encadenar múltiples scopes. Son útiles para filtros comunes como registros activos, por usuario o por fecha.
beforeCreate se ejecuta solo cuando se crea un nuevo registro con create o build seguido de save. beforeSave se ejecuta tanto en creaciones como en actualizaciones. Se usan para lógica transversal como hashear contraseñas antes de guardar, normalizar datos o añadir timestamps personalizados.
Usando limit para el número de registros por página y offset calculado como página menos uno por límite para la paginación por offset. Para tablas muy grandes la paginación por cursor usando where con el id del último registro de la página anterior y order es más eficiente que offset, que requiere leer y descartar filas.
Creando una migración que primero añade la nueva columna con addColumn, luego usa queryInterface.sequelize.query para actualizar los datos existentes con SQL directo, y finalmente aplica los constraints necesarios. Es importante hacer la migración reversible con down implementando el proceso inverso y probando en datos reales antes de producción.
Usando el logging de Sequelize para ver el SQL generado, identificando JOINs innecesarios o subqueries ineficientes, usando attributes para seleccionar solo las columnas necesarias, reemplazando include innecesario por queries separadas cuando sea más eficiente, o usando Sequelize.literal para fragmentos SQL optimizados cuando la API de Sequelize no genera el SQL óptimo.
Activando paranoid: true en la definición del modelo, que añade una columna deletedAt automáticamente. Al llamar a destroy Sequelize establece deletedAt en lugar de eliminar el registro. Las queries normales excluyen automáticamente los registros con deletedAt establecido y se puede incluirlos con paranoid: false cuando sea necesario.
Usando sequelize.transaction() con una función async que recibe el objeto transaction, pasando t en la opción transaction de cada operación dentro del bloque. Si la función resuelve Sequelize hace commit automáticamente y si lanza una excepción hace rollback. Para más control se puede usar la API manual con transaction.commit() y transaction.rollback().

Preguntas avanzadas

Separando los modelos de Sequelize de la lógica de acceso a datos con el patrón Repository, donde cada repositorio encapsula las queries específicas de un dominio. Los servicios usan los repositorios sin conocer los detalles de Sequelize, facilitando el testing con mocks y la posibilidad de cambiar el ORM sin afectar la lógica de negocio.
Cuando el proyecto adopta TypeScript completamente y el tipado estricto es prioritario, cuando la experiencia de desarrollo con el schema declarativo de Prisma aporta valor real al equipo o cuando los tipos generados automáticamente de Prisma reducen bugs significativamente. La migración implica recrear los modelos en schema.prisma, migrar las queries y adaptar las relaciones a la API de Prisma.
Usando schemas separados por tenant en PostgreSQL con sequelize.query('SET search_path TO tenant_schema') por request, o usando una columna tenantId en todas las tablas con un scope global que filtra automáticamente. La estrategia de schema es más aislada pero más compleja de gestionar mientras la columna es más simple pero requiere disciplina para aplicar el filtro consistentemente.
Usando transacciones con el nivel de aislamiento apropiado según los requisitos de concurrencia, implementando optimistic locking con un campo version que se verifica en los updates, y usando constraints de base de datos como unique constraints y foreign keys como última línea de defensa que Sequelize no puede ignorar.
Usando una base de datos de test en memoria con SQLite para tests unitarios que corren rápido sin infraestructura, o una base de datos PostgreSQL en Docker para tests de integración que verifican comportamientos específicos del dialecto. Se aplican migraciones antes de los tests y se limpian los datos entre tests con truncate para garantizar aislamiento.
Configurando el pool con max conexiones según la capacidad del servidor de base de datos, min para mantener conexiones calientes, acquire timeout para evitar esperas indefinidas e idle timeout para liberar conexiones inactivas. Se monitoriza el uso del pool con métricas y se ajusta basándose en el percentil 99 de tiempos de espera en producción.

Errores comunes en entrevistas

Cargar asociaciones dentro de bucles en lugar de usar include en la query principal genera una query por registro. Es el problema de rendimiento más frecuente en aplicaciones Sequelize y no identificarlo en una revisión de código refleja falta de experiencia con el ORM en producción.
Ejecutar múltiples operaciones de base de datos relacionadas sin transacción puede dejar los datos en un estado inconsistente si alguna falla. No saber cuándo las transacciones son necesarias refleja falta de experiencia con operaciones de base de datos en producción.
Confiar ciegamente en que Sequelize genera SQL óptimo sin activar el logging y verificar las queries en desarrollo refleja inexperiencia optimizando aplicaciones con Sequelize. El SQL generado puede ser ineficiente en queries complejas y debe verificarse.
Modificar la base de datos directamente en producción sin migraciones o modificar migraciones ya aplicadas en lugar de crear nuevas refleja desconocimiento de las prácticas de gestión de esquemas en equipos. Las migraciones son la herramienta fundamental para evolucionar el esquema de forma controlada.
Elegir Sequelize para un nuevo proyecto TypeScript sin evaluar Prisma o TypeORM refleja falta de criterio sobre el ecosistema ORM de Node.js actual. Se espera conocer cuándo Sequelize sigue siendo la mejor opción y cuándo las alternativas modernas aportan más valor.
Eliminar registros físicamente cuando el dominio requiere mantener historial o permite recuperación refleja no haber discutido los requisitos de negocio con el equipo. No conocer la opción paranoid de Sequelize para implementarlo de forma estándar refleja inexperiencia con el framework.