Cómo usar Sequelize con Node.js y MySQL
El autor seleccionó el Fondo de Internet Abierto/Libertad de Expresión para recibir una donación como parte del programa Escribir para Donaciones.
Introducción
Sequelize es un mapeador relacional de objetos basado en Node.js que facilita trabajar con bases de datos MySQL, MariaDB, SQLite, PostgreSQL y más. Un mapeador relacional de objetos realiza funciones como manejar registros de la base de datos representando los datos como objetos. Sequelize cuenta con un potente mecanismo de migraciones que puede transformar los esquemas de bases de datos existentes a nuevas versiones. En general, Sequelize ofrece un excelente soporte para la sincronización de bases de datos, carga ansiosa (eager loading), asociaciones, transacciones y migraciones de bases de datos, al tiempo que reduce el tiempo de desarrollo y previene inyecciones SQL.
En este tutorial, instalarás y configurarás Sequelize con MySQL en tu entorno de desarrollo local. A continuación, usarás Sequelize para crear bases de datos y modelos, así como para realizar las operaciones insert, select y delete. Luego, crearás asociaciones de Sequelize para relaciones uno a uno, uno a muchos y muchos a muchos. Finalmente, crearás consultas en bruto de Sequelize para reemplazos de arreglos y objetos. Además de estos conceptos básicos, el tutorial también cubre la configuración segura de la base de datos usando variables de entorno, la gestión segura de cambios en el esquema a través de migraciones y las mejores prácticas para la resolución de problemas y la optimización del rendimiento.
Despliega tus aplicaciones frontend desde GitHub usando la Plataforma de Aplicaciones de Linux-Console.net. Deja que Linux-Console.net se encargue de escalar tu aplicación.
Puntos clave:
- Sequelize ofrece una manera fácil de interactuar con MySQL usando modelos en lugar de SQL sin procesar, lo que hace que las operaciones de la base de datos sean más simples y organizadas.
- Almacenar las credenciales de la base de datos en variables de entorno mantiene la información sensible segura y fuera del control de versiones.
- Usar migraciones para actualizar el esquema es más seguro que depender de
sequelize.sync(), especialmente en entornos de producción. - Sequelize facilita la definición de relaciones uno a uno, uno a muchos y muchos a muchos entre modelos.
- Las consultas SQL en bruto todavía se pueden usar cuando necesitas más control o un mejor rendimiento para operaciones complejas de bases de datos.
- Los problemas más comunes de Sequelize, como fallos de conexión o errores de migración, se pueden resolver verificando las variables de entorno, revisando los registros y utilizando herramientas de MySQL como
EXPLAINySHOW PROCESSLIST. - El rendimiento se puede mejorar optimizando las consultas, agregando índices adecuados, ajustando los grupos de conexiones y almacenando en caché los datos a los que se accede con frecuencia.
- Probar migraciones, añadir manejo de errores y monitorear el rendimiento de la base de datos ayuda a mantener las aplicaciones Sequelize confiables y listas para producción.
Requisitos previos
Para completar este tutorial, necesitarás:
- Un entorno de desarrollo que ejecuta Node.js. Para configurarlo, sigue el tutorial Cómo instalar Node.js y crear un entorno de desarrollo local para tu sistema operativo.
- MySQL instalado en tu entorno de trabajo. Sigue el tutorial Cómo instalar MySQL para tu entorno, incluyendo los pasos para crear un usuario.
- Familiaridad con JavaScript y consultas MySQL, que puedes desarrollar con la serie Cómo Programar en JavaScript y el tutorial Una Introducción a Consultas en MySQL.
- Un conocimiento básico de los conceptos de bases de datos relacionales, que puedes encontrar en el tutorial "Entendiendo las Bases de Datos Relacionales".
Este tutorial se probó en la versión v24.11.1 de Node.js y en la versión 11.6.2 de npm.
Instalación y configuración de Sequelize
En este paso, instalarás Sequelize y crearás la conexión a tu base de datos MySQL. Para hacerlo, primero crearás una aplicación de Node.js. Luego, instalarás Sequelize, configurarás la base de datos MySQL y desarrollarás una aplicación sencilla.
Instalando Sequelize
Comienza creando una carpeta para el proyecto. En este ejemplo, puedes usar hello-world. Una vez creada la carpeta, navega hasta ella usando la terminal:
mkdir hello-world
cd hello-worldLuego, crea una aplicación de ejemplo en Node.js usando el siguiente comando:
npm initA continuación, se le pedirá que responda algunas preguntas de configuración. Use la siguiente información para su configuración. Presione ENTER para usar el valor predeterminado que se muestra y asegúrese de establecer el punto de entrada principal como server.js. Esto crea una estructura de proyecto fácil de mantener.
La salida se verá de la siguiente manera, lo cual llenará el archivo package.json:
{
"name": "hello-world",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}A continuación, crea un archivo vacío server.js dentro de la carpeta del proyecto:
touch server.jsDespués de seguir los pasos anteriores, la estructura final de tu carpeta se verá así:
hello-world/
├─ package.json
├─ server.js
Ahora puedes instalar Sequelize con el siguiente comando:
npm i sequelizeDespués de estas actualizaciones, el archivo package.json ahora se ve así:
{
"name": "hello-world",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"sequelize": "^6.37.7"
}
}En la sección dependencies, ahora verás una dependencia de Sequelize.
Has configurado el proyecto e instalado Sequelize. A continuación, crearás una base de datos de ejemplo para conectarte a ella.
Creando una base de datos de ejemplo
Como parte de los requisitos previos, instalaste y configuraste MySQL, lo que incluyó la creación de un usuario. Ahora crearás una base de datos vacía.
Para hacer eso, primero necesitas iniciar sesión en tu instancia de MySQL. Si estás trabajando de forma remota, puedes usar tu herramienta preferida. Si estás usando una instancia de MySQL que se ejecuta localmente, puedes usar el siguiente comando, reemplazando your_username con tu nombre de usuario de MySQL:
mysql -u your_username -p-u es el nombre de usuario y la opción -p se utiliza si la cuenta está protegida con una contraseña.
El servidor MySQL le pedirá la contraseña de su base de datos. Escriba su contraseña y presione ENTER.
Una vez que hayas iniciado sesión, crea una base de datos llamada hello_world_db usando el siguiente comando:
CREATE DATABASE hello_world_db;Para verificar si ha creado la base de datos correctamente, puede usar este comando:
SHOW DATABASES;
Tu salida será similar a esta:
+--------------------+
| Database |
+--------------------+
| hello_world_db |
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+Después de crear la base de datos de ejemplo, desconéctese del servidor MySQL:
mysql> QUITAhora, necesitas instalar un controlador manual para la base de datos que hayas elegido. Como Sequelize solo proporciona funciones ORM, no incluye controladores de base de datos incorporados. Por lo tanto, necesitarás instalar los controladores según tu preferencia. Para hacer eso, navega al directorio del proyecto usando la terminal e instala el controlador de MySQL en el proyecto usando el siguiente comando:
npm install --save mysql2En este caso, estás usando el controlador para MySQL.
Nota: Dado que este tutorial utiliza MySQL como base de datos, estás usando un controlador para ello. Dependiendo de tu base de datos, puedes instalar manualmente el controlador de la siguiente manera:
npm install --save pg pg-hstore # Postgresnpm install --save mysql2npm install --save mariadbnpm install --save sqlite3npm install --save tedious # Microsoft SQL Server
Ahora que tienes una base de datos de ejemplo, puedes crear tu primera aplicación Sequelize con conectividad a la base de datos.
Conectando a la base de datos MySQL
En esta sección, conectarás la aplicación de Node.js a la base de datos MySQL usando Sequelize.
Para conectarse a la base de datos, abra server.js para editarlo usando nano o su editor de código preferido:
nano server.jsAquí, crearás una conexión a la base de datos en tu aplicación usando una instancia de Sequelize. En el método new Sequelize(), pasa los parámetros del servidor MySQL y las credenciales de la base de datos de la siguiente manera, reemplazando DATABASE_USERNAME y DATABASE_PASSWORD con las credenciales de tu usuario de MySQL:
const Sequelize = require("sequelize");
const sequelize = new Sequelize(
'hello_world_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);host es donde se aloja el servidor MySQL, por lo que necesitarás proporcionar una URL del servidor o una dirección IP. Si estás usando un servidor MySQL instalado localmente, puedes reemplazar DATABASE_HOST con localhost o 127.0.0.1 como valor.
Del mismo modo, si estás utilizando un servidor remoto, asegúrate de reemplazar los valores de conexión a la base de datos con los detalles apropiados del servidor remoto.
Nota: Si está utilizando cualquier otro software de servidor de bases de datos, puede reemplazar el parámetro dialect en consecuencia.
Luego, llama a un método authenticate() basado en promesas para instanciar una conexión de base de datos con la aplicación. Para hacer esto, agrega el siguiente bloque de código a tu archivo server.js:
...
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});El método authenticate() se utiliza para conectarse a la base de datos y verificar si las credenciales proporcionadas son correctas. Aquí, la conexión a la base de datos está abierta por defecto y la misma conexión se puede utilizar para todas las consultas. Cada vez que necesites cerrar la conexión, llama al método sequelize.close() después de esta llamada a authenticate(). Para obtener más información sobre Sequelize, consulta su guía de inicio.
La mayoría de los métodos proporcionados por Sequelize son asincrónicos. Esto significa que puedes ejecutar procesos en tu aplicación mientras un bloque de código asincrónico se está ejecutando. Además, después de la ejecución exitosa de un bloque de código asincrónico, devuelve una promesa, que es el valor retornado al final de un proceso. Por lo tanto, en los bloques de código asincrónicos, puedes usar then(), catch() y finally() para devolver los datos procesados.
En este punto, el archivo server.js se verá de la siguiente manera:
const Sequelize = require("sequelize");
const sequelize = new Sequelize(
'hello_world_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});Guarda y cierra tu archivo.
En el directorio del proyecto, ejecuta la aplicación server.js usando el siguiente comando:
node server.jsTu resultado se verá así:
Connection has been established successfully!Has creado la conexión a la base de datos con éxito.
En este paso, instalaste Sequelize, creaste una base de datos de ejemplo y utilizaste Sequelize para conectarte con la base de datos. A continuación, trabajarás con modelos en Sequelize.
Creando una tabla de base de datos usando Sequelize
Ahora que has creado una base de datos de MySQL de ejemplo, puedes usar Sequelize para crear una tabla y llenarla con datos. En Sequelize, las tablas de la base de datos se conocen como modelos. Un modelo es una abstracción que representa una tabla de la base de datos. Los modelos definen varias cosas para Sequelize, como el nombre de la tabla, los detalles de las columnas y los tipos de datos. En este paso, crearás un modelo de Sequelize para los datos de libros.
Para comenzar, crea un nuevo archivo llamado book.model.js en el directorio del proyecto:
nano book.model.jsDe manera similar al paso anterior, agrega el código de Sequelize para la iniciación de la base de datos con una nueva importación de DataTypes en la parte superior del archivo:
const { Sequelize, DataTypes } = require("sequelize");Sequelize contiene muchos tipos de datos incorporados. Para acceder a esos tipos de datos, debes agregar una importación de DataTypes. Este tutorial hace referencia a algunos tipos de datos que se usan con frecuencia, como STRING, INTEGER y DATEONLY. Para aprender más sobre otros tipos de datos compatibles, puedes consultar la documentación oficial de Sequelize.
Luego, incluye las líneas que usaste anteriormente para crear una conexión a tu base de datos MySQL, actualizando tus credenciales de MySQL correspondientemente:
...
const sequelize = new Sequelize(
'hello_world_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});A continuación, crearás un modelo llamado books, que incluya title, author, release_date y subject ID. Para ello, utiliza el método sequelize.define() como se muestra:
...
const Book = sequelize.define("books", {
title: {
type: DataTypes.STRING,
allowNull: false
},
author: {
type: DataTypes.STRING,
allowNull: false
},
release_date: {
type: DataTypes.DATEONLY,
},
subject: {
type: DataTypes.INTEGER,
}
});El método sequelize.define() define un nuevo modelo, que representa una tabla en la base de datos. Este bloque de código crea una tabla llamada books y almacena los registros de libros según el title, author, release_date y subject.
En este código, allowNull muestra que el valor de la columna del modelo no puede ser null. Del mismo modo, si necesitas establecer un valor así, puedes usar defaultValue: "valor".
A continuación, agregarás el modelo book a tu base de datos. Para hacerlo, usarás el método sync() de la siguiente manera:
...
sequelize.sync().then(() => {
console.log('Book table created successfully!');
}).catch((error) => {
console.error('Unable to create table : ', error);
});En el método sync(), estás pidiendo a Sequelize que haga algunas cosas en la base de datos. Con esta llamada, Sequelize ejecutará automáticamente una consulta SQL en la base de datos y creará una tabla, mostrando el mensaje ¡Tabla Book creada con éxito!.
Como se mencionó, el método sync() es un método basado en promesas, lo que significa que también puede manejar errores. En este bloque de código, verificarás si la tabla se crea correctamente. Si no es así, devolverá un error mediante el método catch y lo imprimirá en la salida.
Nota: Puedes gestionar la sincronización del modelo pasando parámetros force para forzar la creación de una nueva tabla si no existe, o usar una existente. Aquí tienes algunos ejemplos que pueden ser útiles mientras trabajas con Sequelize:
model.sync(): Esto crea la tabla si aún no existe.model.sync({ force: true }): Esto crea la tabla eliminándola si ya existe una tabla con el mismo nombre.
El código final se verá así:
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'hello_world_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});
const Book = sequelize.define("books", {
title: {
type: DataTypes.STRING,
allowNull: false
},
author: {
type: DataTypes.STRING,
allowNull: false
},
release_date: {
type: DataTypes.DATEONLY,
},
subject: {
type: DataTypes.INTEGER,
}
});
sequelize.sync().then(() => {
console.log('Book table created successfully!');
}).catch((error) => {
console.error('Unable to create table : ', error);
});Guarda y cierra tu archivo.
Ejecute su aplicación utilizando el siguiente comando:
node book.model.jsObtendrás la siguiente salida en tu línea de comandos:
Executing (default): SELECT 1+1 AS result
Executing (default): CREATE TABLE IF NOT EXISTS `books` (`id` INTEGER NOT NULL auto_increment , `title` VARCHAR(255) NOT NULL, `author` VARCHAR(255) NOT NULL, `release_date` DATE, `subject` INTEGER, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB;
Connection has been established successfully.
Executing (default): SHOW INDEX FROM `books`
Book table created successfully!En la salida, verás que el registro de retorno contiene el mensaje, ¡Tabla de libros creada con éxito!. Puedes verificar esto revisando tu base de datos para ver la nueva tabla books creada en la base de datos hello_world_db.
Para verificar la creación de la nueva tabla, inicia sesión en tu instancia de MySQL:
mysql -u YOUR_USERNAME -pDespués de ingresar tu contraseña, cambia a la base de datos de ejemplo:
USE hello_world_db;Y luego ejecuta el comando para mostrar las tablas:
SHOW TABLES;Tu salida será similar a esto:
+---------------------------+
| Tables_in_hello_world_db |
+---------------------------+
| books |
+---------------------------+
1 row in set (0.00 sec)Finalmente, desconéctese del servidor MySQL:
mysql> QUITHas verificado que la creación del modelo book fue exitosa. Usando este proceso, puedes crear cualquier cantidad de modelos siguiendo el mismo procedimiento.
En este paso, creaste un modelo en una base de datos e iniciaste el trabajo con un modelo usando métodos integrados. También utilizaste tipos de datos compatibles con Sequelize para definir tu modelo. A continuación, trabajarás con consultas básicas de modelos.
Usando Sequelize para consultas a la base de datos
En este paso, usarás las consultas integradas de Sequelize para inserción, selección, selección con cláusulas condicionales y eliminación.
Insertando un nuevo registro
En el paso anterior, creaste un modelo book dentro de la base de datos. En esta sección, vas a insertar datos en este modelo.
Para empezar, copia el contenido de book.model.js del paso anterior. Crea un nuevo archivo llamado book.controller.js para manejar la lógica de consultas. Agrega el código de book.model.js a book.controller.js.
En book.controller.js, localiza el método sync(). En el método sync(), agrega las siguientes líneas resaltadas:
...
sequelize.sync().then(() => {
console.log('Book table created successfully!');
Book.create({
title: "Clean Code",
author: "Robert Cecil Martin",
release_date: "2021-12-14",
subject: 3
}).then(res => {
console.log(res)
}).catch((error) => {
console.error('Failed to create a new record : ', error);
});
}).catch((error) => {
console.error('Unable to create table : ', error);
});Aquí, insertas un nuevo registro de libro en el modelo books que ya has creado usando el método sync(), el cual permite agregar nuevos registros a modelos previamente creados. Una vez que el método sync() se ejecuta correctamente, se ejecuta el método then(). Dentro del método then(), llamas al método create() para insertar los nuevos registros en el modelo.
Usas el método create() para pasar los datos que necesitas agregar a la base de datos como un objeto. La sección de código resaltada insertará una nueva entrada en tu tabla books existente. En este ejemplo, agregas Clean Code de Robert Cecil Martin, que ha sido categorizado con el ID de subject de 3. Puedes usar el mismo código, actualizado con la información de otros libros, para agregar nuevos registros a tu base de datos.
Guarda y cierra el archivo.
Ejecute la aplicación usando el siguiente comando:
node book.controller.jsTu resultado se verá similar al siguiente:
books {
dataValues:
{ id: 1,
title: 'Clean Code',
author: 'Robert Cecil Martin',
release_date: '2021-12-14',
subject: 3,
updatedAt: 2021-12-14T10:12:16.644Z,
...
}Has insertado un nuevo registro en el modelo que creaste en la base de datos. Puedes continuar agregando múltiples registros usando el mismo proceso.
Seleccionando todos los registros
En esta sección, seleccionará y obtendrá todos los registros de libros de la base de datos usando el método findAll(). Para ello, primero abra book.controller.js y elimine el método Book.create() anterior. En el método sync(), agregue el método Book.findAll() como se muestra:
...
sequelize.sync().then(() => {
Book.findAll().then(res => {
console.log(res)
}).catch((error) => {
console.error('Failed to retrieve data : ', error);
});
}).catch((error) => {
console.error('Unable to create table : ', error);
});
...Guarda y cierra el archivo.
A continuación, ejecute la aplicación nuevamente usando el siguiente comando:
node book.controller.jsTu resultado se verá similar al siguiente:
[
books {
dataValues: {
id: 1,
title: 'Clean Code',
author: 'Robert Cecil Martin',
release_date: '2020-01-01',
subject: 3,
createdAt: 2021-02-22T09:13:55.000Z,
updatedAt: 2021-02-22T09:13:55.000Z
},
_previousDataValues: {
id: 1,
title: 'Clean Code',
author: 'Robert Cecil Martin',
release_date: '2020-01-01',
subject: 3,
createdAt: 2021-02-22T09:13:55.000Z,
updatedAt: 2021-02-22T09:13:55.000Z
},
...
]La salida contiene todos los datos de los libros como un objeto array. Has utilizado con éxito el método findAll() de Sequelize para devolver todos los datos de los libros de la base de datos.
Seleccionando con la cláusula where
En esta sección, seleccionarás valores con condiciones usando la cláusula where. La cláusula where se utiliza para especificar una condición al obtener datos. Para este tutorial, obtendrás un libro por un ID de registro específico de la base de datos usando el método findOne().
Para hacer eso, abre book.controller.js para editarlo, elimina el método findAll() y agrega las siguientes líneas:
...
sequelize.sync().then(() => {
Book.findOne({
where: {
id: 1
}
}).then(res => {
console.log(res)
}).catch((error) => {
console.error('Failed to retrieve data : ', error);
});
}).catch((error) => {
console.error('Unable to create table : ', error);
});Aquí, seleccionas un registro de libro específico de la base de datos usando el método findOne() con la opción where. En este ejemplo, estás recuperando los datos del libro cuyo id es igual a 1.
Guarda y cierra el archivo.
A continuación, ejecuta la aplicación:
node book.controller.jsTu resultado se verá similar al siguiente:
books {
dataValues: {
id: 1,
title: 'Clean Code',
author: 'Robert Cecil Martin',
release_date: '2020-01-01',
subject: 'Science',
createdAt: 2021-02-22T09:13:55.000Z,
updatedAt: 2021-02-22T09:13:55.000Z
},
...
}Has utilizado con éxito las cláusulas where para obtener datos de los modelos de Sequelize. Puedes usar la cláusula where en la aplicación de la base de datos para capturar datos condicionales.
Eliminando un registro
Para eliminar un registro específico del modelo de base de datos, utiliza el método destroy() con la opción where. Para hacer esto, abre book.controller.js, elimina el método findOne() y agrega las siguientes líneas resaltadas:
...
sequelize.sync().then(() => {
Book.destroy({
where: {
id: 2
}
}).then(() => {
console.log("Successfully deleted record.")
}).catch((error) => {
console.error('Failed to delete record : ', error);
});
}).catch((error) => {
console.error('Unable to create table : ', error);
});Aquí, eliminas un registro de libro de la base de datos utilizando el método destroy() con la opción where y pasando el id del libro que deseas eliminar. Vas a eliminar el registro del libro cuyo id sea igual a 2.
Guarda y cierra el archivo.
A continuación, ejecuta la aplicación:
node book.controller.jsTu salida se verá de la siguiente manera:
Successfully deleted record.El registro ha sido eliminado.
En este paso, experimentaste con tu modelo de base de datos y la consulta de modelos. Iniciaste la base de datos, creaste modelos, insertaste registros, recuperaste registros, recuperaste registros con condiciones usando la cláusula where y eliminaste registros seleccionados. Con este conocimiento de Sequelize, ahora crearás asociaciones en Sequelize. Después de eso, podrás definir y trabajar con una variedad de relaciones usando modelos de Sequelize.
Creando asociaciones usando Sequelize
En este paso, utilizarás los tipos de asociación estándar que Sequelize admite: uno a uno, uno a muchos y muchos a muchos. Usarás datos de ejemplo sobre estudiantes, cursos y niveles de calificación.
Sequelize utiliza tipos de asociación basados en las siguientes relaciones de base de datos:
relación uno a uno: Una relación uno a uno significa que un registro en una tabla está asociado con exactamente un registro en otra tabla. En términos de Sequelize, puedes usar las asociaciones
belongsTo()yhasOne()para crear este tipo de relación.Relación uno a muchos: Una relación uno a muchos significa que un registro en una tabla está asociado con múltiples registros en otra tabla. Con Sequelize, puedes usar los métodos de asociación
hasMany()para crear este tipo de relación.Relación muchos a muchos: Una relación muchos a muchos significa que varios registros en una tabla están asociados con varios registros en otra tabla. Con Sequelize, puedes usar asociaciones
belongsToMany()para crear este tipo de relación.
Antes de crear estas asociaciones, primero crearás una nueva base de datos llamada student_db y añadirás nuevos modelos y algunos datos de ejemplo para estudiantes, cursos y nivel de grado.
Para crear la base de datos, sigue el mismo proceso que en Instalación y Configuración de Sequelize para iniciar sesión en MySQL y crear una base de datos llamada student_db. Una vez que se haya creado la nueva base de datos, cierra sesión en MySQL. A continuación, comenzarás a crear las asociaciones de la base de datos.
Creando una relación uno a uno con belongsTo()
En esta sección, crearás una relación uno a uno usando modelos de Sequelize. Imagina que quieres obtener los detalles de un estudiante junto con su nivel de grado. Dado que un estudiante solo puede tener un nivel de grado, este tipo de asociación es una relación uno a uno y puedes usar el método belongsTo().
Nota: Hay una diferencia entre belongsTo() y hasOne(). belongsTo() añadirá la foreignKey en la tabla de origen, mientras que hasOne() la añadirá en la tabla de destino. En cualquier caso, si se utilizan ambas relaciones al mismo tiempo, funcionará como relaciones uno a uno bidireccionales en Sequelize.
El método belongsTo() te permite crear una relación uno a uno entre dos modelos de Sequelize. En este ejemplo, estás usando los modelos Student y Grade.
Crea un nuevo archivo llamado one_to_one.js. Como hiciste en la sección anterior, Conectando a la Base de Datos MySQL, incluye las líneas para crear una conexión a la base de datos y autenticar a tu usuario de MySQL en la parte superior del archivo. Asegúrate de actualizar las credenciales de MySQL según sea necesario:
const { Sequelize, DataTypes } = require("sequelize");
const sequelize = new Sequelize(
'student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});En esta sección, crearás tres modelos en la nueva base de datos student_db: Student, Grade y Course. Comenzarás creando los modelos Student y Grade. Más adelante en este paso, crearás el modelo Courses.
Para el modelo Student, añade el siguiente bloque de código a one_to_one.js:
...
const Student = sequelize.define("students", {
student_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false
}
});Este modelo de estudiante contiene dos columnas: student_id y name.
A continuación, añade un bloque de código para el modelo Grade:
...
const Grade = sequelize.define("grades", {
grade: {
type: DataTypes.INTEGER,
allowNull: false
}
});El modelo Grade contiene la columna grade.
Para demostrar las asociaciones, necesitarás agregar datos de ejemplo a la base de datos. Para eso, utilizarás el método bulk(). En lugar de insertar los datos en las filas una por una, el método bulkCreate() te permite insertar varias filas en tus modelos de base de datos a la vez.
Así que ahora, importa los datos de Grade y Student a sus respectivos modelos en la base de datos como se muestra:
...
const grade_data = [{grade : 9}, {grade : 10}, {grade : 11}]
const student_data = [
{name : "John Baker", gradeId: 2},
{name : "Max Butler", gradeId: 1},
{name : "Ryan Fisher", gradeId: 3},
{name : "Robert Gray", gradeId: 2},
{name : "Sam Lewis", gradeId: 1}
]
sequelize.sync({ force: true }).then(() => {
Grade.bulkCreate(grade_data, { validate: true }).then(() => {
Student.bulkCreate(student_data, { validate: true }).then(() => {
…
}).catch((err) => { console.log(err); });
}).catch((err) => { console.log(err); });
}).catch((error) => {
console.error('Unable to create the table : ', error);
});Aquí, proporcionas datos de ejemplo e insertas los datos en los modelos Student y Grade. Con tu base de datos, modelos y datos de ejemplo en su lugar, estás listo para crear asociaciones.
En one-to-one.js, añade la siguiente línea debajo del bloque student_data:
...
Student.belongsTo(Grade);A continuación, necesitarás comprobar si la asociación está funcionando correctamente. Para hacer eso, puedes recuperar los datos de todos los estudiantes con los niveles de grado asociados pasando el parámetro include dentro del método findAll().
Dado que necesitas obtener el nivel de grado del estudiante, pasarás Grade como el modelo. En el método sequelize.sync(), añade las líneas resaltadas como se muestra:
...
sequelize.sync({ force: true }).then(() => {
Grade.bulkCreate(grade_data, { validate: true }).then(() => {
Student.bulkCreate(student_data, { validate: true }).then(() => {
Student.findAll({
include: [{
model: Grade
}]
}).then(result => {
console.log(result)
}).catch((error) => {
console.error('Failed to retrieve data : ', error);
});
}).catch((err) => { console.log(err); });
}).catch((err) => { console.log(err); });
}).catch((error) => {
console.error('Unable to create the table : ', error);
});El código completo se ve de la siguiente manera:
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});
const Student = sequelize.define("students", {
student_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false
}
});
const Grade = sequelize.define("grades", {
grade: {
type: DataTypes.INTEGER,
allowNull: false
}
});
const grade_data = [{grade : 9}, {grade : 10}, {grade : 11}]
const student_data = [
{name : "John Baker", gradeId: 2},
{name : "Max Butler", gradeId: 1},
{name : "Ryan Fisher", gradeId: 3},
{name : "Robert Gray", gradeId: 2},
{name : "Sam Lewis", gradeId: 1}
]
// One-To-One association
Student.belongsTo(Grade);
sequelize.sync({ force: true }).then(() => {
Grade.bulkCreate(grade_data, { validate: true }).then(() => {
Student.bulkCreate(student_data, { validate: true }).then(() => {
Student.findAll({
include: [{
model: Grade
}]
}).then(result => {
console.log(result)
}).catch((error) => {
console.error('Failed to retrieve data : ', error);
});
}).catch((err) => { console.log(err); });
}).catch((err) => { console.log(err); });
}).catch((error) => {
console.error('Unable to create the table : ', error);
});Guarda y cierra tu archivo.
Ejecute el archivo utilizando el siguiente comando:
node one_to_one.jsLa salida será larga, y verás los datos de todos los estudiantes con los niveles de grado. Aquí hay un fragmento de la salida que muestra los datos de los estudiantes:
students {
dataValues:
{ student_id: '3e786a8f-7f27-4c59-8e9c-a8c606892288',
name: 'Sam Lewis',
createdAt: 2021-12-16T08:49:38.000Z,
updatedAt: 2021-12-16T08:49:38.000Z,
gradeId: 1,
grade: [grades] },
_previousDataValues:
...Dependiendo de las herramientas de línea de comandos que estés utilizando, la salida puede mostrarse en una vista ampliada o no. Si se trata de una vista ampliada, imprime el objeto grade expandido como salida.
En esta sección, creaste una relación uno a uno utilizando la llamada al método Student.belongsTo(Grade); y obtuviste los detalles según la asociación que creaste.
Creando una relación de uno a muchos con hasMany()
En esta sección, crearás una relación de uno a muchos utilizando modelos de Sequelize. Imagina que deseas obtener todos los estudiantes asociados con un nivel de grado seleccionado. Dado que un nivel de grado específico puede tener múltiples estudiantes, esta es una relación de uno a muchos.
Para comenzar, copia el contenido de one_to_one.js en un nuevo archivo llamado one_to_many.js. En one_to_many.js, elimina las líneas después del bloque student_data. Tu archivo one_to_many.js se verá así:
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});
const Student = sequelize.define("students", {
student_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false
}
});
const Grade = sequelize.define("grades", {
grade: {
type: DataTypes.INTEGER,
allowNull: false
}
});
const grade_data = [ {grade : 9}, {grade : 10}, {grade : 11}]
const student_data = [
{name : "John Baker", gradeId: 2},
{name : "Max Butler", gradeId: 1},
{name : "Ryan Fisher", gradeId: 3},
{name : "Robert Gray", gradeId: 2},
{name : "Sam Lewis", gradeId: 1}
]Después del bloque student_data, usa el método hasMany() para crear una nueva relación:
...
Grade.hasMany(Student)El método hasMany() te permite crear una relación de uno a muchos entre dos modelos de Sequelize. Aquí, estás usando los modelos Grade y Student.
A continuación, agrega el método sequelize.sync() junto con el método findAll() debajo de la línea hasMany():
...
sequelize.sync({ force: true }).then(() => {
Grade.bulkCreate(grade_data, { validate: true }).then(() => {
Student.bulkCreate(student_data, { validate: true }).then(() => {
Grade.findAll({
where: {
grade: 9
},
include: [{
model: Student
}]
}).then(result => {
console.dir(result, { depth: 5 });
}).catch((error) => {
console.error('Failed to retrieve data : ', error);
});
}).catch((err) => { console.log(err); });
}).catch((err) => { console.log(err); });
}).catch((error) => {
console.error('Unable to create table : ', error);
});Aquí estás intentando acceder a todos los estudiantes de un nivel de grado en particular; en este caso, todos los estudiantes de grado 9. También añadiste el modelo Student en la opción include.
Aquí está el código completo:
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});
const Student = sequelize.define("students", {
student_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false
}
});
const Grade = sequelize.define("grades", {
grade: {
type: DataTypes.INTEGER,
allowNull: false
}
});
const grade_data = [ {grade : 9}, {grade : 10}, {grade : 11}]
const student_data = [
{name : "John Baker", gradeId: 2},
{name : "Max Butler", gradeId: 1},
{name : "Ryan Fisher", gradeId: 3},
{name : "Robert Gray", gradeId: 2},
{name : "Sam Lewis", gradeId: 1}
]
// One-To-Many relationship
Grade.hasMany(Student);
sequelize.sync({ force: true }).then(() => {
Grade.bulkCreate(grade_data, { validate: true }).then(() => {
Student.bulkCreate(student_data, { validate: true }).then(() => {
Grade.findAll({
where: {
grade: 9
},
include: [{
model: Student
}]
}).then(result => {
console.dir(result, { depth: 5 });
}).catch((error) => {
console.error('Failed to retrieve data : ', error);
});
}).catch((err) => { console.log(err); });
}).catch((err) => { console.log(err); });
}).catch((error) => {
console.error('Unable to create table : ', error);
});Guarda y cierra tu archivo.
Ejecuta el archivo con el siguiente comando:
node one_to_many.jsLa salida se verá similar a la siguiente. Será bastante larga, pero todos los estudiantes de grado 9 serán devueltos de la siguiente manera:
[ grades {
dataValues:
{ id: 1,
grade: 9,
createdAt: 2021-12-20T05:12:31.000Z,
updatedAt: 2021-12-20T05:12:31.000Z,
students:
[ students {
dataValues:
{ student_id: '8a648756-4e22-4bc0-8227-f590335f9965',
name: 'Sam Lewis',
createdAt: 2021-12-20T05:12:31.000Z,
updatedAt: 2021-12-20T05:12:31.000Z,
gradeId: 1 },
...
students {
dataValues:
{ student_id: 'f0304585-91e5-4efc-bdca-501b3dc77ee5',
name: 'Max Butler',
createdAt: 2021-12-20T05:12:31.000Z,
updatedAt: 2021-12-20T05:12:31.000Z,
gradeId: 1 },
...En esta sección, creaste una relación de uno a muchos utilizando la llamada al método Grade.hasMany(Student);. En el resultado, obtuviste los detalles según la asociación que creaste.
Creando relaciones Muchos a Muchos con belongsToMany()
En esta sección, crearás relaciones de muchos a muchos usando modelos de Sequelize. Como ejemplo, imagina una situación en la que los estudiantes están inscritos en cursos. Un estudiante puede inscribirse en muchos cursos y un curso puede tener muchos estudiantes. Esta es una relación de muchos a muchos. Para implementarlo usando Sequelize, usarás los modelos Student, Course y StudentCourse con el método belongsToMany().
Para comenzar, crea un archivo llamado many_to_many.js y agrega los bloques de código de inicialización y autentificación de la base de datos como sigue. (Puedes reutilizar los bloques de código del ejemplo anterior one_to_many.js). Asegúrate de actualizar los valores de conexión a la base de datos resaltados según sea necesario.
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});A continuación, crearás los modelos de base de datos para relaciones de muchos a muchos: Student y Course. Luego agregarás algunos datos de ejemplo a esos modelos.
...
const Student = sequelize.define("students", {
student_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
name: {
type: DataTypes.STRING,
allowNull: false
}
});
const Course = sequelize.define("courses", {
course_name: {
type: DataTypes.STRING,
allowNull: false
}
});
const StudentCourse = sequelize.define('StudentCourse', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false
}
});
const course_data = [
{course_name : "Science"},
{course_name : "Maths"},
{course_name : "History"}
]
const student_data = [
{name : "John Baker", courseId: 2},
{name : "Max Butler", courseId: 1},
{name : "Ryan Fisher", courseId: 3},
{name : "Robert Gray", courseId: 2},
{name : "Sam Lewis", courseId: 1}
]
const student_course_data = [
{studentId : 1, courseId: 1},
{studentId : 2, courseId: 1},
{studentId : 2, courseId: 3},
{studentId : 3, courseId: 2},
{studentId : 1, courseId: 2},
]Aquí, creas los modelos Student y Course y proporcionas algunos datos de ejemplo. También estableces un courseID, que usarás para recuperar estudiantes según este tipo de relación.
Finalmente, definiste un nuevo modelo llamado StudentCourse, que gestiona los datos de la relación entre Student y Course. En este ejemplo, studentId 1 está inscrito en courseId 1 y courseId 2.
Has completado la inicialización de la base de datos y añadido datos de ejemplo a la base de datos. A continuación, crea relaciones muchos a muchos usando el método belongsToMany() como se muestra:
...
Course.belongsToMany(Student, { through: 'StudentCourse'})
Student.belongsToMany(Course, { through: 'StudentCourse'})Dentro del método belongsToMany(), pasas la configuración through con el nombre del modelo como opción de configuración. En este caso, es StudentCourse. Esta es la tabla que gestiona las relaciones de muchos a muchos.
Finalmente, puedes comprobar si la asociación está funcionando correctamente obteniendo todos los datos del curso con los estudiantes asociados. Harás esto pasando el parámetro include dentro del método findAll(). Agrega las siguientes líneas a many_to_many.js:
...
sequelize.sync({ force: true }).then(() => {
Course.bulkCreate(course_data, { validate: true }).then(() => {
Student.bulkCreate(student_data, { validate: true }).then(() => {
StudentCourse.bulkCreate(student_course_data, { validate: true }).then(() => {
Course.findAll({
include: {
model: Student,
},
}).then(result => {
console.log(result);
}).catch((error) => {
console.error('Failed to retrieve data : ', error);
});
}).catch((error) => {
console.log(error);
});
}).catch((error) => {
console.log(error);
});
}).catch((error) => {
console.log(error);
});
}).catch((error) => {
console.error('Unable to create table : ', error);
});El código completo se ve de la siguiente manera:
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});
const Student = sequelize.define("students", {
student_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
name: {
type: DataTypes.STRING,
allowNull: false
}
});
const Course = sequelize.define("courses", {
course_name: {
type: DataTypes.STRING,
allowNull: false
}
});
const StudentCourse = sequelize.define('StudentCourse', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false
}
});
const course_data = [
{course_name : "Science"},
{course_name : "Maths"},
{course_name : "History"}
]
const student_data = [
{name : "John Baker", courseId: 2},
{name : "Max Butler", courseId: 1},
{name : "Ryan Fisher", courseId: 3},
{name : "Robert Gray", courseId: 2},
{name : "Sam Lewis", courseId: 1}
]
const student_course_data = [
{studentId : 1, courseId: 1},
{studentId : 2, courseId: 1},
{studentId : 2, courseId: 3},
{studentId : 3, courseId: 2},
{studentId : 1, courseId: 2},
]
Course.belongsToMany(Student, { through: 'StudentCourse'})
Student.belongsToMany(Course, { through: 'StudentCourse'})
sequelize.sync({ force: true }).then(() => {
Course.bulkCreate(course_data, { validate: true }).then(() => {
Student.bulkCreate(student_data, { validate: true }).then(() => {
StudentCourse.bulkCreate(student_course_data, { validate: true }).then(() => {
Course.findAll({
include: {
model: Student,
},
}).then(result => {
console.log(result);
}).catch((error) => {
console.error('Failed to retrieve data : ', error);
});
}).catch((error) => {
console.log(error);
});
}).catch((error) => {
console.log(error);
});
}).catch((error) => {
console.log(error);
});
}).catch((error) => {
console.error('Unable to create table : ', error);
});Guarda y cierra el archivo.
Ejecute el archivo usando el siguiente comando:
node many_to_many.jsLa salida será larga, pero se verá algo similar a lo siguiente:
[ courses {
dataValues:
{ id: 1,
course_name: 'Science',
createdAt: 2022-05-11T04:27:37.000Z,
updatedAt: 2022-05-11T04:27:37.000Z,
students: [Array] },
_previousDataValues:
{ id: 1,
course_name: 'Science',
createdAt: 2022-05-11T04:27:37.000Z,
updatedAt: 2022-05-11T04:27:37.000Z,
students: [Array] },
_changed: Set {},
_options:
{ isNewRecord: false,
_schema: null,
_schemaDelimiter: '',
include: [Array],
includeNames: [Array],
includeMap: [Object],
includeValidated: true,
attributes: [Array],
raw: true },
isNewRecord: false,
students: [ [students], [students] ] },
courses {
dataValues:
{ id: 2,
course_name: 'Maths',
createdAt: 2022-05-11T04:27:37.000Z,
updatedAt: 2022-05-11T04:27:37.000Z,
students: [Array] },
_previousDataValues:
...Como puedes ver en este resultado, se recuperaron los cursos con estudiantes asociados. Dentro del bloque courses, verás valores de id separados que indican cada curso. Por ejemplo, id: 1 está conectado con course_name: Science para la clase de Ciencias, mientras que id: 2 es la clase de Matemáticas, y así sucesivamente.
En la base de datos, puedes ver las tres tablas generadas con los datos de ejemplo que insertaste.
En este paso, utilizaste Sequelize para crear asociaciones uno a uno, uno a muchos y muchos a muchos. A continuación, trabajarás con consultas en bruto.
Trabajando con consultas en bruto
En este paso, trabajarás con consultas sin procesar en Sequelize. En pasos anteriores, utilizaste los métodos integrados de Sequelize, como insert() y findAll(), para manejar la inserción y selección de datos en la base de datos. Puede que hayas notado que esos métodos siguen un patrón específico para escribir una consulta. Sin embargo, con el uso de consultas sin procesar, no necesitas preocuparte por los métodos y patrones integrados de Sequelize. Usando tu conocimiento de consultas SQL, puedes realizar una variedad de consultas en Sequelize, desde las más simples hasta las más avanzadas.
Aquí hay un ejemplo de consultas sin procesar que realizan la acción de seleccionar todos los valores de una tabla en particular, eliminar los valores seleccionados según la condición y actualizar la tabla con los valores proporcionados.
SELECT * FROM table_name;
DELETE FROM table_name WHERE condition;
UPDATE table_name SET y = 42 WHERE x = 12;En Sequelize, las consultas en crudo se pueden usar principalmente con dos metodologías: reemplazo por arreglo y reemplazo por objeto. Cuando estás pasando valores a la consulta SQL, puedes usar un arreglo o un objeto para hacer ese reemplazo.
Antes de escribir una consulta en crudo, primero necesitarás proporcionar datos de estudiantes en una base de datos de ejemplo. Siguiendo la sección anterior, Creación de una Base de Datos de Ejemplo, inicia sesión en MySQL, crea una base de datos llamada sample_student_db y cierra sesión en MySQL.
A continuación, agregarás algunos datos en bruto para empezar a trabajar con consultas en crudo. Crea un nuevo archivo llamado add_student_records.js y agrega los siguientes bloques de código, que contienen los métodos de Sequelize previamente discutidos de authenticate(), sync() y bulkCreate().
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'sample_student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});
const Student = sequelize.define("students", {
student_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false
}
});
const student_data = [
{name : "John Baker"},
{name : "Max Butler"},
{name : "Ryan Fisher"},
{name : "Robert Gray"},
{name : "Sam Lewis"}
]
sequelize.sync({ force: true }).then(() => {
Student.bulkCreate(student_data, { validate: true }).then((result) => {
console.log(result);
}).catch((error) => {
console.log(error);
});
}).catch((error) => {
console.error('Unable to create table : ', error);
});Aquí, inicias la conexión a la base de datos, creas el modelo e insertas algunos registros de estudiantes dentro de la nueva base de datos.
Guarda y cierra el archivo.
A continuación, ejecuta este script usando el siguiente comando:
node add_student_records.jsLa salida será algo similar a lo siguiente. Será bastante larga, pero se devolverán todos los registros de estudiantes que hayas insertado de la siguiente manera. Ten en cuenta que, dado que el student_id es un valor UUID (Identificador Único Universal) autogenerado, será diferente según el usuario.
Executing (default): SELECT 1+1 AS result
Executing (default): DROP TABLE IF EXISTS `students`;
Connection has been established successfully.
Executing (default): DROP TABLE IF EXISTS `students`;
Executing (default): CREATE TABLE IF NOT EXISTS `students` (`student_id` CHAR(36) BINARY , `name` VARCHAR(255) NOT NULL, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, PRIMARY KEY (`student_id`)) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `students`
Executing (default): INSERT INTO `students` (`student_id`,`name`,`createdAt`,`updatedAt`) VALUES ('45d1f26c-ba76-431f-ac5f-f41282351710','John Baker','2022-06-03 07:27:49','2022-06-03 07:27:49'),('1cb4e34d-bfcf-4a97-9624-e400b9a1a5f2','Max Butler','2022-06-03 07:27:49','2022-06-03 07:27:49'),('954c576b-ba1c-4dbc-a5c6-8eaf22bbbb04','Ryan Fisher','2022-06-03 07:27:49','2022-06-03 07:27:49'),('e0f15cd3-0025-4032-bfe8-774e38e14c5f','Robert Gray','2022-06-03 07:27:49','2022-06-03 07:27:49'),('826a0ec9-edd0-443f-bb12-068235806659','Sam Lewis','2022-06-03 07:27:49','2022-06-03 07:27:49');
[
students {
dataValues: {
student_id: '45d1f26c-ba76-431f-ac5f-f41282351710',
name: 'John Baker',
createdAt: 2022-06-03T07:27:49.453Z,
updatedAt: 2022-06-03T07:27:49.453Z
},
_previousDataValues: {
name: 'John Baker',
student_id: '45d1f26c-ba76-431f-ac5f-f41282351710',
createdAt: 2022-06-03T07:27:49.453Z,
updatedAt: 2022-06-03T07:27:49.453Z
},
…En la siguiente sección, aplicarás consultas directas usando uno de los resultados de student_id en el bloque de código de arriba. Copíalo para tenerlo en las próximas secciones, donde usarás el método query() para reemplazos en arreglos y objetos.
Reemplazo de matriz
En esta sección, usarás el método query() para reemplazar un array. Con este método, Sequelize puede ejecutar consultas SQL en bruto o ya preparadas.
Para comenzar, copia el contenido del archivo server.js del Paso 1, ya que eso incluye el método Sequelize() de inicialización y la creación de la base de datos. Pega el contenido en un nuevo archivo llamado array_raw_query.js. Actualiza el nombre de la base de datos a sample_student_db:
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'sample_student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});Al final del archivo, agrega el siguiente bloque de código para reemplazar un arreglo, asegurándote de reemplazar REPLACE_STUDENT_ID con el valor de student_id que copiaste en la sección anterior.
...
sequelize.query(
'SELECT * FROM students WHERE student_id = ?',
{
replacements: ['REPLACE_STUDENT_ID'],
type: sequelize.QueryTypes.SELECT
}
).then(result => {
console.log(result);
}).catch((error) => {
console.error('Failed to insert data : ', error);
});Para la sustitución en arreglos, se pasa al método query() la consulta SQL y el objeto de configuración. Este contiene el valor y el tipo de replacements. Para las sustituciones, se pasan los datos como un arreglo y se capturan esos valores usando el símbolo de interrogación (?).
A continuación, dado que necesitas obtener datos sobre un estudiante específico, se pasa el student_id como el segundo parámetro. Después de eso, pasas el par clave-valor type: sequelize.QueryTypes.SELECT, que puedes usar para seleccionar datos de la base de datos.
También hay otros tipos, como QueryTypes.UPDATE y QueryTypes.DELETE. Dependiendo del requisito, puedes seleccionar el tipo que se adapte a tu propósito.
Lo siguiente muestra el bloque de código completo. Aquí te conectas a la base de datos y recuperas los datos del estudiante seleccionado utilizando una consulta directa.
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'sample_student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});
sequelize.query(
'SELECT * FROM students WHERE student_id = ?',
{
replacements: ['REPLACE_STUDENT_ID'],
type: sequelize.QueryTypes.SELECT
}
).then(result => {
console.log(result);
}).catch((error) => {
console.error('Failed to insert data : ', error);
});Guarda y cierra tu archivo.
A continuación, puedes ejecutar este script usando el siguiente comando:
node array_raw_query.jsVerás una salida similar a la siguiente:
Connection has been established successfully.
[ { student_id: 'STUDENT_ID_YOU_RETRIEVED',
name: 'Robert Gray',
createdAt: 2022-05-06T13:14:50.000Z,
updatedAt: 2022-05-06T13:14:50.000Z } ]Debido al student_id seleccionado, sus valores de salida pueden variar.
Reemplazo de objeto
A primera vista, la sustitución de objetos es similar a la sustitución de arrays, pero el patrón de pasar datos a la consulta sin procesar es diferente. En la opción de sustitución, pasas los datos como un objeto, y en la opción de consulta, usas valores como :key.
Para comenzar, crea un nuevo archivo llamado object_raw_query.js y pega los bloques de código completos del archivo server.js, actualizando la base de datos a sample_student_db.
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'sample_student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});Luego, agrega el siguiente bloque de código al final del nuevo archivo object_raw_query.js:
...
sequelize.query(
'SELECT * FROM students WHERE student_id = :id',
{
replacements: { id: 'REPLACE_STUDENT_ID' },
type: sequelize.QueryTypes.SELECT
}
).then(result => {
console.log(result);
}).catch((error) => {
console.error('Failed to insert data : ', error);
});Aquí, obtienes los datos de un estudiante seleccionado usando el método de reemplazo de objetos. Creas un objeto replacement, estableciendo el id como la información del estudiante que deseas obtener: { id: '<mark>REPLACE_STUDENT_ID</mark>' }.
En el query(), indicas: 'SELECT * FROM students WHERE student_id=:id'. Usando el método query(), pasas el valor de reemplazo como un objeto, por lo que este método se conoce como reemplazo por objeto.
Aquí está el código completo:
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'sample_student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});
sequelize.query(
'SELECT * FROM students WHERE student_id = :id',
{
replacements: { id: 'REPLACE_STUDENT_ID' },
type: sequelize.QueryTypes.SELECT
}
).then(result => {
console.log(result);
}).catch((error) => {
console.error('Failed to insert data : ', error);
});Guarda y cierra el archivo.
A continuación, ejecuta este script usando el siguiente comando:
node object_raw_query.jsLa salida se verá similar a la siguiente:
Connection has been established successfully.
[ { student_id: 'STUDENT_ID_YOU_RETRIEVED',
name: 'Robert Gray',
createdAt: 2022-05-06T13:14:50.000Z,
updatedAt: 2022-05-06T13:14:50.000Z } ]Debido al student_id seleccionado, sus valores de salida pueden variar.
En este paso, trabajaste con consultas sin procesar de Sequelize utilizando dos metodologías diferentes: sustitución por arreglos y sustitución por objetos.
Configuración de variables de entorno para conexiones de base de datos seguras
Hasta este punto, has almacenado las credenciales de la base de datos directamente en tu código. Aunque este enfoque funciona para experimentos locales, no es seguro. Codificar las credenciales directamente expone información sensible, como contraseñas y nombres de host de la base de datos, y aumenta el riesgo de filtraciones cuando el código se comparte o se despliega. En esta sección, aprenderás cómo almacenar estas credenciales de manera segura usando variables de entorno.
¿Por qué usar variables de entorno?
Las variables de entorno actúan como parámetros de configuración externos que tu aplicación puede leer en tiempo de ejecución. En lugar de incrustar las credenciales directamente en tu código, se almacenan en tu sistema o en un archivo de configuración separado que nunca se sube al control de versiones. Este enfoque ofrece múltiples beneficios:
- Seguridad: Tus credenciales permanecen ocultas en los repositorios públicos y en los sistemas de control de versiones.
- Flexibilidad: Cada entorno (desarrollo, pruebas, producción) puede usar una configuración de base de datos diferente sin alterar el código fuente.
- Mantenibilidad: La configuración centralizada permite actualizaciones fáciles; si la contraseña de la base de datos cambia, solo necesitas modificar un archivo o variable.
- Cumplimiento: Esta práctica se alinea con la metodología de la aplicación de doce factores, que promueve la gestión de configuración específica del entorno para aplicaciones escalables.
Configuración de variables de entorno
Utilizarás el paquete dotenv para administrar tus variables de entorno en Node.js. Esta biblioteca lee las variables de un archivo .env y las inyecta en el objeto process.env de Node.js, haciéndolas accesibles en toda tu aplicación.
Instala el paquete dotenv ejecutando el siguiente comando:
npm install dotenvDespués de la instalación, crea un nuevo archivo llamado .env en el directorio raíz de tu proyecto. Este archivo contendrá toda la información sensible necesaria para la conexión a la base de datos.
Aquí tienes un ejemplo de configuración de archivo .env para tu configuración de Sequelize:
DB_NAME=hello_world_db
DB_USER=your_username
DB_PASSWORD=your_password
DB_HOST=localhost
DB_DIALECT=mysqlCada variable corresponde a un parámetro específico que Sequelize utiliza para establecer la conexión.
Asegurando el archivo .env
Es crucial que tu archivo .env nunca aparezca en el control de versiones. Para evitar esto, agrégalo a tu archivo .gitignore para que Git lo ignore al hacer commits de los cambios:
.envEn entornos de producción, las variables de entorno se configuran típicamente directamente en la configuración de la plataforma de alojamiento o en el gestor de secretos, en lugar de en un archivo .env local.
Actualizando tu configuración de Sequelize
Ahora que has definido las variables de entorno, actualiza la inicialización de Sequelize para usarlas. Comienza importando y configurando dotenv en la parte superior de tu archivo principal del servidor (por ejemplo, server.js):
require('dotenv').config();
const Sequelize = require('sequelize');
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASSWORD,
{
host: process.env.DB_HOST,
dialect: process.env.DB_DIALECT
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database:', error);
});- La línea
require('dotenv').config()carga todas las variables definidas en el archivo.envenprocess.env. - Sequelize lee las variables de entorno al inicializar una nueva instancia.
- Esto garantiza que ninguna información sensible, como nombres de usuario o contraseñas, aparezca en tu código.
Este enfoque mejora la seguridad y permite que diferentes entornos utilicen configuraciones únicas simplemente cambiando los valores de las variables de entorno.
Verificando la configuración
Una vez que hayas actualizado tu configuración, puedes verificar la conexión ejecutando tu aplicación de Node.js:
node server.jsSi tu archivo .env está configurado correctamente y el servidor MySQL está en funcionamiento, verás la siguiente salida:
Connection has been established successfully.Si aparece un error, verifica que:
- Las credenciales en tu archivo
.envcoinciden con las de tu instancia de MySQL. - El servidor MySQL está funcionando y es accesible desde tu host.
- El archivo
.envse encuentra en la raíz de tu proyecto y está correctamente formateado.
Mejores prácticas para la configuración segura
Usar variables de entorno es un primer paso importante hacia una gestión de configuración segura. Para garantizar una seguridad y confiabilidad continuas, considere las siguientes mejores prácticas adicionales:
- Utiliza configuraciones separadas por entorno: Mantén diferentes archivos
.envparadevelopment,stagingyproduction. Puedes nombrarlos.env.development,.env.stagingy.env.production. - Utiliza gestores de secretos: Para entornos de producción, utiliza gestores de secretos en lugar de almacenar secretos en archivos.
- Validar variables de entorno: Utiliza una utilidad de configuración o un script de inicio que verifique si las variables necesarias están definidas antes de que la aplicación se ejecute. Esto ayuda a prevenir errores en tiempo de ejecución.
- Rota las credenciales regularmente: Actualiza las credenciales de la base de datos de manera periódica para reducir el impacto de posibles filtraciones.
- Restringir el acceso a la base de datos: Configure los roles de usuario de la base de datos para que tengan los privilegios mínimos necesarios para la aplicación.
Al implementar variables de entorno y seguir estas prácticas, aseguras que tu aplicación Sequelize mantenga tanto la flexibilidad como la seguridad en los entornos de desarrollo y producción.
Mejores Prácticas de Migración y Sincronización
Al evolucionar un esquema de base de datos, tienes dos enfoques principales con Sequelize: escribir y ejecutar migraciones explícitas, o usar sequelize.sync() para que Sequelize genere automáticamente los cambios en el esquema. Ambos enfoques son válidos, pero sirven para diferentes propósitos y tienen sus desventajas. Vamos a entender cuándo usar cada uno, cómo escribir migraciones seguras y cómo ejecutarlas de manera confiable en desarrollo y producción.
Migraciones vs sequelize.sync(): cuándo usar cada una
Migraciones (recomendadas para producción): Las migraciones son cambios explícitos y versionados en tu esquema. Se almacenan en el control de código fuente, son revisadas mediante revisión de código y se ejecutan como parte de tu proceso de despliegue. Utiliza migraciones en producción para cualquier cambio en el esquema que afecte la integridad o disponibilidad de los datos.
sequelize.sync()(usar solo para desarrollo y prototipos):sync()es conveniente para desarrollo local, pruebas y prototipos rápidos porque puede crear o ajustar tablas automáticamente. Sin embargo, no usesync({ force: true })nisync({ alter: true })en producción: estas opciones pueden eliminar datos o realizar cambios inseguros sin una vía de reversión controlada.
Herramientas: sequelize-cli y ejecutores de migración
La CLI oficial, sequelize-cli, es la forma más sencilla de gestionar migraciones con Sequelize. Para equipos que necesitan más control o flujos de trabajo personalizados, umzug es una alternativa popular.
Para instalar e inicializar la CLI en tu proyecto, ejecuta:
npm install --save-dev sequelize-cli
npx sequelize-cli initEsto crea las siguientes carpetas:
config/ # Stores database configuration files
models/ # Stores your Sequelize model definitions
migrations/ # Stores migration files
seeders/ # Stores seeder files for inserting initial dataDeberías agregar estas carpetas al control de versiones (excepto cualquier archivo que contenga credenciales). Esto asegura que cada entorno ejecute los mismos cambios en la base de datos de manera consistente.
Crear y ejecutar migraciones
Para crear un nuevo archivo de migración, utiliza el siguiente comando:
npx sequelize-cli migration:generate --name add-isbn-to-booksEsto crea un archivo con marca de tiempo en la carpeta migrations/. Abre el archivo y define lo que debe suceder cuando apliques la migración (up) y cómo deshacerla (down):
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.addColumn('books', 'isbn', {
type: Sequelize.STRING
});
},
async down(queryInterface) {
await queryInterface.removeColumn('books', 'isbn');
}
};- La función
upaplica la migración (agrega la columna). - La función
downrevierte el cambio (elimina la columna).
Ejecute la migración usando:
npx sequelize-cli db:migratePara revertir la última migración, usa:
npx sequelize-cli db:migrate:undoTambién puedes deshacer todas las migraciones en desarrollo ejecutando:
npx sequelize-cli db:migrate:undo:allEscribiendo migraciones seguras e idempotentes
Sigue estas reglas al crear migraciones:
Envuelve las operaciones en transacciones cuando la base de datos lo permita. Esto asegura que la migración se complete por completo o se revierta completamente en caso de error.
module.exports = { async up(queryInterface, Sequelize) { return queryInterface.sequelize.transaction(async (t) => { await queryInterface.addColumn('books', 'isbn', { type: Sequelize.STRING }, { transaction: t }); }); }, async down(queryInterface) { return queryInterface.sequelize.transaction(async (t) => { await queryInterface.removeColumn('books', 'isbn', { transaction: t }); }); } };Mantén las migraciones pequeñas y enfocadas. Cada migración debe realizar un solo cambio lógico (agregar una columna, crear un índice, renombrar una columna). Las migraciones más pequeñas son más fáciles de revisar, probar y revertir.
Sé defensivo e idempotente. Cuando sea apropiado, verifica la existencia para evitar fallos en entornos con desviaciones. Usa
queryInterface.describeTable()o consultas directas para proteger las operaciones.Evita operaciones destructivas en un solo paso. Por ejemplo, para eliminar una columna de manera segura, agrega la nueva columna, rellénala con datos, cambia las lecturas a la nueva columna en el código de la aplicación, y solo luego elimina la columna antigua en una migración posterior.
Migraciones prolongadas y sin tiempo de inactividad
Las tablas grandes y las operaciones de índices pueden causar bloqueos. Utiliza estas estrategias para minimizar el impacto:
- Agrega primero las columnas que permiten valores nulos. Agregar una columna que permite valores nulos suele ser rápido. Evita agregar columnas con valores predeterminados complejos que obliguen a reescribir toda la tabla.
- Rellena en pequeños lotes. Implementa un trabajo en segundo plano para llenar las nuevas columnas en fragmentos en lugar de ejecutar un único
UPDATEmasivo. - Crea índices sin bloqueos exclusivos siempre que sea posible. Utiliza funciones específicas de la base de datos (por ejemplo, Postgres
CONCURRENTLYy el reciente DDL en línea de MySQL) mediante SQL en bruto en las migraciones. - Implementación de funciones con bandera. Despliega el código de la aplicación que lee la nueva columna detrás de una bandera de función. Una vez que se complete el relleno de datos y las pruebas sean exitosas, cambia la bandera y más adelante elimina el campo antiguo.
Reversiones y correcciones de emergencia
- Proporciona una migración
down. Cadaupdebe tener undowninverso que documente cómo revertir el cambio. Incluso si rara vez ejecutasdownen producción, sirve como un plan de reversión claro. - Planifica los retrocesos de emergencia. Si una migración causa problemas, prepárate para revertir primero el código de la aplicación y restaurar la base de datos desde las copias de seguridad si es necesario. Prueba regularmente los procedimientos de restauración.
Iniciando datos y cambios en el esquema
- Mantén los seeders mínimos y estables. Usa seeders solo para filas esenciales de búsqueda o configuración. Evita sembrar grandes volúmenes de datos de negocio mediante migraciones.
- Separe las semillas de las migraciones estructurales. Si necesita poblar datos que dependen de un cambio en el esquema, prefiera un seeder posterior para que las responsabilidades permanezcan claras.
CI, revisión de código y automatización de despliegues
Ejecuta las migraciones primero en CI y staging. Agrega un paso en CI que ejecute las migraciones en una base de datos efímera para detectar errores temprano.
Trata las migraciones como código. Revisa y analiza los archivos de migración en las solicitudes de extracción. Incluye verificaciones automáticas siempre que sea posible.
Automatiza la ejecución de migraciones durante los despliegues. Asegúrate de que las migraciones se ejecuten antes de que los procesos de la aplicación acepten tráfico. Ejemplo de scripts en
package.json:"scripts": { "migrate": "npx sequelize-cli db:migrate", "start": "node server.js", "deploy": "npm run migrate && npm run start" }
En implementaciones con contenedores, ejecuta las migraciones como un contenedor de inicialización o en un hook de despliegue para que los procesos web comiencen solo después de que las migraciones se completen.
Probando migraciones
- Prueba ambas direcciones. Ejecuta
db:migratey luegodb:migrate:undo:alllocalmente y en CI para validar queupydownfuncionen correctamente. - Utiliza bases de datos desechables para las pruebas. Prefiere bases de datos efímeras o contenedores para ejecuciones de prueba deterministas.
Seguir estas mejores prácticas garantiza que tus migraciones de Sequelize sean confiables, rastreables y seguras para proyectos a largo plazo.
Solución de problemas y consejos de rendimiento
Incluso con una configuración sólida, las aplicaciones de Sequelize a veces pueden enfrentar problemas inesperados, como errores de conexión a la base de datos, fallos en las migraciones, consultas lentas, bloqueos o conflictos de concurrencia. Esta sección ofrece una guía paso a paso para identificar y solucionar estos problemas, junto con las mejores prácticas de rendimiento que te ayudan a mantener tu aplicación estable y eficiente.
Solución de problemas comunes
1. Errores de conexión
Problema: Sequelize no puede conectarse a la base de datos MySQL y genera errores como SequelizeConnectionError: connect ECONNREFUSED, Access denied for user o SequelizeHostNotFoundError.
Cómo arreglar:
Comienza por confirmar que los detalles de tu conexión son correctos. Revisa cuidadosamente los valores en tu archivo .env, incluyendo el nombre de la base de datos, el nombre de usuario, la contraseña, el host y el puerto. Incluso un error tipográfico pequeño o un espacio al final puede hacer que Sequelize no pueda conectarse.
A continuación, asegúrate de que tu servidor MySQL esté en funcionamiento y escuchando en el puerto predeterminado (3306). Puedes probar la conectividad iniciando sesión manualmente:
mysql -u your_username -p -h your_host -P 3306Si este comando falla, el problema probablemente esté en tu servicio de MySQL o en las credenciales.
Si estás usando el paquete dotenv, asegúrate de que require('dotenv').config() aparezca antes de que se inicialice Sequelize. Este paso garantiza que todas las variables de entorno estén disponibles en tiempo de ejecución.
Finalmente, para las conexiones remotas, verifica la configuración de tu firewall o las reglas del grupo de seguridad. El servidor MySQL debe aceptar conexiones entrantes desde el host de tu aplicación.
2. Fallos de migración
Problema: Ejecutas npx sequelize-cli db:migrate o sequelize.sync() y encuentras errores como tablas faltantes, columnas duplicadas o desajustes en el esquema.
Cómo arreglar:
Los problemas de migración suelen ocurrir cuando los archivos se ejecutan en el orden incorrecto o contienen errores en las operaciones SQL. Sequelize procesa las migraciones basándose en las marcas de tiempo en los nombres de los archivos, así que confirma que los archivos estén nombrados correctamente.
Si una migración falla, inspecciona el mensaje de error en detalle. Luego, revierte de manera segura la última migración usando:
npx sequelize-cli db:migrate:undoDespués de corregir el archivo de migración, ejecútalo de nuevo.
Siempre envuelve tu lógica de migración dentro de una transacción para asegurar la atomicidad: o todos los cambios se realizan correctamente o ninguno. Antes de ejecutar migraciones en producción, pruébalas a fondo en una base de datos de pruebas o local para confirmar que son correctas.
3. Problemas de sincronización del modelo
Problema: Tus modelos de Sequelize y el esquema de la base de datos no coinciden, lo que provoca columnas faltantes, tipos de datos incorrectos o tablas desactualizadas.
Cómo arreglar:
Evita usar sequelize.sync({ force: true }) en producción, ya que eliminará y recreará las tablas, causando pérdida de datos irreversible. De manera similar, usa sequelize.sync({ alter: true }) con precaución; podría modificar los esquemas de forma impredecible.
En su lugar, prefiere migraciones explícitas que definan claramente cada cambio de esquema. Para comparar tus modelos con la estructura actual de la base de datos, usa:
await queryInterface.describeTable('tableName');Esto ayuda a identificar desajustes antes de que afecten la producción.
4. Errores de consulta y de tiempo de espera
Problema: Ves errores relacionados con el tiempo de espera, como Tiempo de espera de conexión o Demasiadas conexiones.
Cómo arreglar:
Los problemas de tiempo de espera suelen ser causados por grupos de conexiones agotados o conexiones no cerradas. Asegúrate de que todas las consultas de Sequelize se esperen correctamente para que las conexiones vuelvan al grupo después de su uso.
También puedes ajustar la configuración del grupo de Sequelize para que se adapte mejor a la carga de trabajo de tu aplicación:
const sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASSWORD, {
host: DB_HOST,
dialect: 'mysql',
pool: {
max: 10,
min: 0,
acquire: 30000,
idle: 10000
}
});Si tu aplicación maneja muchos usuarios concurrentes, considera aumentar el parámetro max_connections de MySQL en la configuración de tu base de datos. Monitorea las conexiones activas usando SHOW PROCESSLIST; en MySQL para detectar fugas o sesiones no utilizadas.
5. Rendimiento lento de las consultas
Problema: Algunas consultas toman mucho más tiempo de lo esperado, lo que provoca respuestas lentas de la API o alta latencia.
Cómo diagnosticar:
Comienza identificando cuáles consultas son lentas. Puedes registrar todas las consultas que Sequelize ejecuta activando el registro temporalmente:
const sequelize = new Sequelize(..., { logging: console.log });Luego, utiliza la sentencia EXPLAIN de MySQL para entender cómo la base de datos ejecuta la consulta:
EXPLAIN SELECT * FROM users WHERE email = 'user@example.com';Si el resultado muestra un "escaneo completo de tabla", la consulta carece de un índice.
También puedes habilitar el registro de consultas lentas de MySQL para capturar automáticamente las operaciones lentas:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;Cómo arreglar:
Agrega índices a las columnas que se consultan con frecuencia (especialmente aquellas usadas en cláusulas WHERE, JOIN y ORDER BY). Evita SELECT *, en su lugar, obtén explícitamente solo los campos que necesitas usando la opción attributes.
Si Sequelize genera consultas ineficientes con uniones innecesarias, reescribe la consulta usando SQL en crudo. Para informes grandes o consultas de análisis, divídelas en pasos más pequeños o utiliza herramientas de almacenamiento en caché como Redis para guardar resultados repetitivos.
6. Interbloqueos y errores de concurrencia
Problema: Recibes errores como Se encontró un interbloqueo al intentar obtener el bloqueo; intenta reiniciar la transacción, especialmente bajo alta carga.
Cómo diagnosticar:
Verifica el estado de InnoDB en MySQL para identificar qué consultas o tablas estuvieron involucradas:
SHOW ENGINE INNODB STATUS\GEsta salida incluye información detallada sobre bloqueos recientes y esperas de bloqueo. También puede habilitar registros generales o de transacciones para revisar la secuencia de consultas que conducen al problema.
Cómo arreglar:
Los bloqueos mutuos ocurren cuando dos o más transacciones están esperando los bloqueos de las demás. Para minimizarlos:
Mantén las transacciones lo más cortas posible. No llames a servicios externos ni realices cálculos pesados mientras mantienes un bloqueo.
Siempre accede a tablas y filas en el mismo orden en todas las transacciones para evitar esperas circulares.
Para bloqueos recurrentes, implemente un mecanismo simple de reintento con retroceso exponencial:
async function runWithRetry(fn, retries = 3) { for (let i = 0; i < retries; i++) { try { return await fn(); } catch (err) { if (isDeadlockError(err) && i < retries - 1) { await sleep(100 * (i + 1)); continue; } throw err; } } }Usa bloqueos a nivel de fila (
SELECT ... FOR UPDATE) solo cuando sea necesario y libéralos de inmediato.Divide las transacciones grandes en lotes más pequeños para reducir la contención.
Ajusta los niveles de aislamiento de las transacciones si es apropiado, pero solo después de confirmar que no afectará la consistencia de los datos.
Consejos para la Optimización del Rendimiento
Los problemas de rendimiento a menudo provienen de consultas ineficientes, índices faltantes o un manejo deficiente de las conexiones. Aquí hay algunos consejos prácticos para mejorar el rendimiento de Sequelize sin sacrificar la claridad o la mantenibilidad.
1. Optimizar consultas
Escribe consultas que soliciten solo lo necesario. Obtener datos innecesarios aumenta el uso de memoria y ralentiza las respuestas. Usa attributes para limitar los campos:
const users = await User.findAll({ attributes: ['id', 'name'] });Siempre filtra los resultados usando columnas indexadas y evita búsquedas generales como LIKE '%term%' siempre que sea posible.
2. Utiliza los índices de manera efectiva
Los índices hacen que las búsquedas sean más rápidas, pero pueden ralentizar las escrituras. Agrega índices solo donde ofrezcan beneficios medibles. Analiza el rendimiento de las consultas con EXPLAIN de MySQL para verificar las mejoras antes y después de añadir un índice:
await queryInterface.addIndex('users', ['email']);3. Ajustar el grupo de conexiones
El grupo de conexiones determina cuántas conexiones de base de datos mantiene Sequelize.
- Para aplicaciones pequeñas, generalmente son suficientes de 5 a 10 conexiones.
- Para sistemas de alta carga, aumente el tamaño del grupo y ajuste los tiempos de espera para evitar cuellos de botella en las conexiones. Controle el uso regularmente usando
SHOW PROCESSLIST;de MySQL.
4. Habilitar el registro y la supervisión
Activa temporalmente el registro de Sequelize para revisar las consultas SQL. Utiliza el registro de consultas lentas de MySQL para identificar las consultas que superen tu umbral de rendimiento. Para el seguimiento en tiempo real, integra herramientas de monitoreo como PM2, New Relic o Datadog para medir la latencia y el uso de recursos.
5. Almacenar en caché consultas frecuentes
Almacena en caché los resultados de los datos que no cambian con frecuencia. Herramientas como Redis o Memcached reducen las consultas a la base de datos y mejoran los tiempos de respuesta, especialmente en cargas de trabajo con muchas lecturas.
6. Paginizar resultados
Los conjuntos de resultados grandes consumen una cantidad significativa de memoria. Siempre pagine las consultas en las API:
const users = await User.findAll({ limit: 50, offset: 100 });La paginación mantiene las respuestas ligeras y consistentes bajo carga.
7. Usa consultas en bruto para operaciones complejas
Si la abstracción de Sequelize resulta en SQL ineficiente para un caso de uso específico, ejecuta una consulta SQL directa:
const [results] = await sequelize.query('SELECT name FROM users WHERE active = 1');Usa consultas en bruto con moderación y documéntalas claramente para el mantenimiento futuro.
Preguntas frecuentes
1. ¿Qué es Sequelize en Node.js?
Sequelize es un mapeador objeto-relacional (ORM) para Node.js que proporciona una API de alto nivel basada en JavaScript para trabajar con bases de datos SQL como MySQL, PostgreSQL, SQLite y MariaDB. En lugar de escribir SQL en bruto para cada operación, defines modelos que se corresponden con tablas de la base de datos y usas métodos de los modelos para insertar, consultar, actualizar y eliminar registros. Sequelize también admite asociaciones (relaciones entre modelos), transacciones, migraciones y consultas en SQL cuando necesitas SQL directo.
2. ¿Cómo conecto Sequelize a MySQL?
Para conectar Sequelize a MySQL, creas una instancia de Sequelize con las credenciales y opciones de tu base de datos. En un archivo de Node.js, requiere Sequelize, opcionalmente carga variables de entorno, y luego instáncialo con el nombre de la base de datos, nombre de usuario, contraseña y opciones de host/dialecto. Por ejemplo:
require('dotenv').config();
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASSWORD,
{
host: process.env.DB_HOST || 'localhost',
dialect: 'mysql'
}
);
await sequelize.authenticate();
console.log('Connected to the database successfully!');Siempre mantén las credenciales fuera del control de versiones (por ejemplo, en un archivo .env o en un gestor de secretos).
3. ¿Cómo creo y sincronizo modelos en Sequelize?
En Sequelize, los modelos representan tablas. Los defines usando sequelize.define() o extendiendo la clase Model. Después de eso, puedes sincronizarlos con tu base de datos para que Sequelize cree las tablas automáticamente (ideal para desarrollo). Aquí tienes un pequeño ejemplo:
const { DataTypes } = require('sequelize');
const Book = sequelize.define('Book', {
title: { type: DataTypes.STRING, allowNull: false },
author: { type: DataTypes.STRING, allowNull: false },
releaseDate: DataTypes.DATEONLY
});
// Create the table if it does not exist
await sequelize.sync();Para entornos de producción, es mejor usar migraciones para tener control de versiones sobre los cambios en el esquema.
4. ¿Cómo uso Sequelize con Express?
En una app de Express, inicializa Sequelize en un archivo separado (como db.js), exporta la instancia conectada y los modelos, e impórtalos donde los necesites en tus manejadores de rutas o controladores. Usa async/await en los manejadores de rutas para llamar a los métodos de los modelos de Sequelize, y maneja los errores con try/catch o un middleware de errores. Así es como se ve eso en la práctica:
// db.js
const sequelize = new Sequelize(...);
const User = sequelize.define(...);
module.exports = { sequelize, User };
// app.js
const { User } = require('./db');
app.get('/users', async (req, res) => {
const users = await User.findAll();
res.json(users);
});Asegúrate de ejecutar las migraciones o llamar a sequelize.sync() antes de iniciar tu servidor Express, para que las tablas de tu base de datos existan cuando lleguen las solicitudes.
5. ¿Cuál es la diferencia entre Sequelize y las consultas SQL sin procesar?
Sequelize te permite escribir lógica de bases de datos en JavaScript, mientras que SQL puro significa escribir sentencias SQL directamente. Sequelize hace que tu código sea más limpio y fácil de mantener porque puedes definir modelos y relaciones una vez y usarlos en toda tu aplicación. SQL puro te da más control y, a veces, puede ser más rápido para consultas muy complejas.
En resumen, usa Sequelize para la mayoría de las tareas diarias y utiliza SQL sin procesar cuando necesites optimizar el rendimiento o ejecutar algo que Sequelize no pueda expresar fácilmente.
6. ¿Cómo puedo definir asociaciones en Sequelize?
Sequelize facilita la descripción de las relaciones entre tablas. Puedes configurar relaciones uno a uno, uno a muchos o muchos a muchos utilizando métodos simples como hasOne, hasMany y belongsToMany. Por ejemplo:
// One to one
User.hasOne(Profile);
Profile.belongsTo(User);
// One to many
Author.hasMany(Book);
Book.belongsTo(Author);
// Many to many
Student.belongsToMany(Course, { through: 'StudentCourse' });
Course.belongsToMany(Student, { through: 'StudentCourse' });Una vez definido, puedes usar include en tus consultas para obtener automáticamente los datos relacionados.
7. ¿Cómo manejo las migraciones y los cambios de esquema?
En desarrollo, puedes usar sequelize.sync() para crear tablas rápidamente. Pero en producción, siempre debes usar migraciones. Las migraciones son archivos versionados que describen cómo cambia el esquema de tu base de datos con el tiempo, similar a cómo los commits de Git rastrean los cambios en el código.
Puedes crear una migración con el CLI de Sequelize:
npx sequelize-cli migration:generate --name add-users-tableCada migración tiene métodos up y down, uno para aplicar el cambio y otro para deshacerlo. Ejecuta tus migraciones con:
npx sequelize-cli db:migrateDe esta manera, todos en tu equipo (y cada despliegue) mantienen la misma estructura de base de datos.
Usa sequelize.sync() para experimentos rápidos, pero pásate a las migraciones una vez que tu aplicación esté en producción, porque son más seguras, reversibles y fáciles de rastrear.
Conclusión
En este tutorial, configuraste Sequelize con Node.js y MySQL para gestionar datos a través de modelos, asociaciones y migraciones. También protegiste las credenciales de tu base de datos con variables de entorno y exploraste las mejores prácticas para la resolución de problemas y la optimización del rendimiento.
Con estos pasos, ahora tienes una base sólida para construir y mantener aplicaciones Node.js seguras y eficientes que utilizan Sequelize y MySQL para la gestión de datos.
Para aprender más sobre Sequelize, consulta la documentación del producto. Para más tutoriales de Node.js como este, revisa los siguientes artículos:
- Cómo Configurar un Servidor GraphQL en Node.js con Apollo Server y Sequelize
- Cómo implementar almacenamiento en caché en Node.js usando Redis
- Cómo crear un servidor web en Node.js con el módulo HTTP
Gracias por aprender con la comunidad de Linux-Console.net.
