Cómo usar un ORM en Node.js
Cómo usar un ORM en Node.js — RO Solutions Blog
En este blog hablaré un poco sobre los ORM's, sus beneficios (pros) y limitaciones (cons), qué es Sequelize y cómo usarlo.
Object-Relational Mapping
Es una técnica para convertir datos entre el sistema de tipos del lenguaje de programación y la base de datos. Como su nombre lo indica, esto va dirigido solamente a las base de datos relacional (SQL). Esto crea un efecto “objeto base de datos virtual” sobre la base de datos relacional, este efecto es lo que nos permite manipular la base de datos a través del código. Object - Hace referencia al/los objeto(s) que podemos usar en nuestro lenguaje. Relational - Hace referencia a nuestro Sistema Gestor de Base de Datos (MySQL, MSSQL, PostgreSQL). Mapping - Hace referencia a la conexión entre los objetos y las tablas.
tl;dr ORM es una técnica que nos permite hacer queries y manipular datos de la base de datos desde un lenguaje de programación.
Pros
Abstracto: Diseño de una estructura o modelo aislado de la base de datos.
Portable: Te permite transportar la estructura de tu ORM a cualquier DBMS.
Anidación de datos: En caso de que una tabla tenga una o varias relaciones con otras.
Cons
Lento: Si se compara el tiempo de respuesta entre un raw query y un query hecho por objetos, raw query es mucho mas rápido debido a que no existe una capa (mapping).
Complejidad: Algunas veces necesitaremos hacer queries complejos, por suerte Sequelize te permite ejecutar raw queries.
¿Qué es Sequelize?
Sequelize es un ORM basado en promesas para Node.js. Soporta PostgreSQL, MySQL, SQLite y MSSQL, y entrega características sólidas de transacciones, relaciones entre tablas, mecanismos de migraciones y carga de datos, y más.
¿Por qué decídí utilizar Sequelize?
Sequelize maneja sus objetos como promesas, algo que va de la mano con el event loop de Node.js. Ahora les mostraré cómo crear y migrar tablas, cargar datos, y cómo consultar estos datos. Si quieren checar el código, pueden clonarlo desde aquí.
Requerimientos
Node.js 8.0+
MySQL 5.7+
Primero instalaremos globalmente el módulo sequelize-cli:
npm i -g sequelize-cliDespués crearemos una carpeta donde contenga nuestra aplicación, crearemos un archivo js e instalaremos sequelize y su dialecto (en este caso MySQL):
mkdir howto-sequelize && cd howto-sequelize
touch index.js
npm init -y
npm i -S sequelize mysql2Ahora tenemos que iniciar el proyecto con sequelize-cli:
sequelize initsequelize-cli nos creó una estructura base en la raíz de nuestro proyecto:

Si revisamos el archivo ./config/config.json, vemos que tenemos 3 opciones de conexión a una base de datos, modifiquemos la opción “development”:
"development": {
"username": "root",
"password": "tucontraseña",
"database": "howto-sequelize",
"host": "127.0.0.1",
"dialect": "mysql",
"operatorsAliases": false,
"dialectOptions": {
"charset": "utf8mb4"
},
"logging": true,
"benchmark": true
}Ahora revisemos el archivo ./models/index.js. Este archivo tiene como función crear una nueva instancia de Sequelize cada vez que sea llamado, y tiene como variable de entorno default a “development”. Creemos nuestra base de datos con el siguiente comando:
sequelize db:createMuy bien! Ahora empecemos a crear nuestros modelos:
sequelize model:generate --name Usuario --attributes nombre:string,apellidoP:string,apellidoM:string,email:string
sequelize model:generate --name LenguajeP --attributes nombre:string
sequelize model:generate --name Usuario_LenguajeP --attributes UsuarioId:integer,LenguajePId:integerDespués de crear nuestros modelos hay que hacer la relación entre usuarios y lenguajes. ./models/usuario.js
'use strict';
module.exports = (sequelize, DataTypes) => {
var Usuario = sequelize.define('Usuario', {
nombre: DataTypes.STRING,
apellidoP: DataTypes.STRING,
apellidoM: DataTypes.STRING,
email: DataTypes.STRING
}, {});
Usuario.associate = function(models) {
Usuario.belongsToMany(models.LenguajeP, {
through: 'Usuario_LenguajeP',
as: 'lenguajesProgramacion',
foreignKey: 'UsuarioId',
})
};
return Usuario;
};Analicemos esto: nuestro modelo Usuario ejecuta una función belongsToMany que indica que un usuario puede pertenecer a varios lenguajes. through indica la tabla por la que tiene que cruzar para encontrar estas relaciones, as es el nombre de la propiedad que nos entregara estas relaciones, y foreignKey marca por cuál columna queremos que busque estas relaciones. ./models/lenguajep.js
'use strict';
module.exports = (sequelize, DataTypes) => {
var LenguajeP = sequelize.define('LenguajeP', {
nombre: DataTypes.STRING
}, {});
LenguajeP.associate = function (models) {
LenguajeP.belongsToMany(models.Usuario, {
through: 'Usuario_LenguajeP',
as: 'usuarios',
foreignKey: 'LenguajePId',
})
};
return LenguajeP;
};Hacemos lo mismo con los lenguajes (LenguajeP), pero ahora apuntando al modelo Usuario. Recordemos que Sequelize no detecta cambios en modelos para crear migraciones automáticamente, así que nosotros las creamos:
sequelize migration:generate --name relation-many-to-manyEsto nos generó un archivo nuevo con un esqueleto en nuestras migraciones, ahora tenemos que modificarlo:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return [
queryInterface.addConstraint('Usuario_LenguajeP', ['UsuarioId'], {
type: 'FOREIGN KEY',
name: 'FK_UsuarioLenguajeP_Usuario_1',
references: { table: 'Usuario', field: 'id' },
onDelete: 'no action',
onUpdate: 'no action',
}),
queryInterface.addConstraint('Usuario_LenguajeP', ['LenguajePId'], {
type: 'FOREIGN KEY',
name: 'FK_UsuarioLenguajeP_LenguajeP_1',
references: { table: 'LenguajeP', field: 'id' },
onDelete: 'no action',
onUpdate: 'no action',
}),
]
},
down: (queryInterface, Sequelize) => {
return [
queryInterface.removeConstraint('Usuario_LenguajeP', 'FK_UsuarioLenguajeP_Usuario_1'),
queryInterface.removeConstraint('Usuario_LenguajeP', 'FK_UsuarioLenguajeP_LenguajeP_1'),
]
}
};Este archivo exporta un objeto con 2 propiedades, up y down. La propiedad up está encargada de entregar una promesa que altere los datos (crear tablas, relaciones, campos), y down hace lo contrario, revierte los cambios de up. Ahora toca correr los scripts de migración:
sequelize db:migrateSi hay un error sobre tablas no encontradas, recuerda que sequelize-cli maneja tablas en plural por default. Cambia los nombres a plural en la migración (Usuario_LenguajePs, Usuarios, LenguajePs), luego:
sequelize db:migrate:undo:all
sequelize db:migrate
Las migraciones fueron exitosas! Ahora tenemos que poblar estas tablas, sequelize-cli utiliza las seeds (semillas). Vamos a crear 3 archivos seed:
sequelize seed:generate --name usuario
sequelize seed:generate --name lenguajep
sequelize seed:generate --name usuario_lenguajepEstas semillas están ubicadas en la carpeta ./seeders, hay que poner los datos que deseamos cargar en la base de datos. Semilla usuario:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.bulkInsert('Usuarios', [
{
nombre: 'John',
apellidoP: 'Q',
apellidoM: 'Doe',
email: '[email protected]',
createdAt: new Date(),
updatedAt: new Date(),
},
], {});
},
down: (queryInterface, Sequelize) => {
return queryInterface.bulkDelete('Usuarios', null, {});
}
};Semilla lenguajep:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.bulkInsert('LenguajePs', [
{ nombre: 'Java', createdAt: new Date(), updatedAt: new Date() },
{ nombre: 'JavaScript', createdAt: new Date(), updatedAt: new Date() },
{ nombre: 'PHP', createdAt: new Date(), updatedAt: new Date() },
{ nombre: 'Go', createdAt: new Date(), updatedAt: new Date() },
{ nombre: 'C++', createdAt: new Date(), updatedAt: new Date() },
], {});
},
down: (queryInterface, Sequelize) => {
return queryInterface.bulkDelete('LenguajePs', null, {});
}
};Semilla usuario_lenguajep:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.bulkInsert('Usuario_LenguajePs', [
{ UsuarioId: 1, LenguajePId: 2, createdAt: new Date(), updatedAt: new Date() },
{ UsuarioId: 1, LenguajePId: 5, createdAt: new Date(), updatedAt: new Date() },
], {});
},
down: (queryInterface, Sequelize) => {
return queryInterface.bulkDelete('Usuario_LenguajePs', null, {});
}
};Ahora hay que cargar estos datos a la base de datos:
sequelize db:seed:allMuy bien! Ya vimos cómo inicializar un proyecto con sequelize, crear modelos, asignar relaciones, crear migraciones, correr y deshacer migraciones, crear semillas y cargarlas a la base de datos, solo nos falta hacer queries. Vamos a nuestro index.js en raíz y escribimos la siguiente función:
const db = require('./models');
const main = async () => {
try {
const usuarios = await db.Usuario.findAll({
include: [{
model: db.LenguajeP,
as: 'lenguajesProgramacion',
attributes: { exclude: ['createdAt', 'updatedAt'] },
through: { attributes: [] },
}],
});
console.log(JSON.stringify(usuarios));
process.exit();
} catch (error) {
console.log(error);
}
};
main();Lo guardamos y ejecutamos el programa y deberíamos recibir un JSON con una lista de usuarios con sus datos y lenguajes:
[
{
"id": 1,
"nombre": "John",
"apellidoP": "Q",
"apellidoM": "Doe",
"email": "[email protected]",
"createdAt": "2018-08-04T19:35:19.000Z",
"updatedAt": "2018-08-04T19:35:19.000Z",
"lenguajesProgramacion": [
{ "id": 2, "nombre": "JavaScript" },
{ "id": 5, "nombre": "C++" }
]
}
]Aprendimos sequelize-cli y lo básico de Sequelize, ahora podemos crear un API que ejecute acciones CRUD para que nuestros clientes la consuman!
