Cómo usar un ORM en Node.js
ProgramaciónSoftwareDesarrollo

Cómo usar un ORM en Node.js

Cómo usar un ORM en Node.js — RO Solutions Blog

·RO Solutions

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-cli

Despué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 mysql2

Ahora tenemos que iniciar el proyecto con sequelize-cli:

sequelize init

sequelize-cli nos creó una estructura base en la raíz de nuestro proyecto:

Node.js ORM query result
Node.js ORM query result

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:create

Muy 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:integer

Despué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-many

Esto 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:migrate

Si 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
Código Node.js funcionando
Código Node.js funcionando

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_lenguajep

Estas 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:all

Muy 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!

orm node.js

RO Solutions

¿Necesitas ayuda con tu proyecto?

Cotiza tu proyecto
WhatsApp