Sequelize en tiempos de SQL

Ivan Díaz
Nowports Tech and Product
4 min readMar 30, 2020

Realicé esta publicación como recordatorio personal sobre una forma de resolver un problema en el uso de Sequelize, las asociaciones son algo recurrentes cuando manipulas base de datos. Sé que a muchos colegas les ayudará y ahorrará demasiadas horas.

Cuando usamos Sequelize, es muy frecuente la ejecución de consultas básicas; pero llega un momento en que queremos ir más allá del uso de “model.find(), model.create()”, realizar consultas más complejas y nos preguntamos si este ORM nos puede ayudar a manejarlo. La respuesta es sí, pero su documentación puede ser un dolor de cabeza al inicio.

Vamos a realizar lo que en SQL llamamos “consulta JOIN”. En Sequelize significa que, con unas cuantas líneas de código adicionales, obtendremos una tabla determinada con sus datos asociados. Básicamente es unir dos tablas.

Las asociaciones

Lugares y Festivales (uno a muchos)

//MODELO PARA FESTIVALESmodule.exports = (sequelize, DataTypes) => {
const Festival = sequelize.define('Festival', {
confirmed: {
type: DataTypes.STRING,
allowNull: true,
},
}, {
classMethods: {
associate(models) {
Festival.belongsTo(models.Lugar, {
foreignKey: {
name: 'idFestival',
allowNull: true,
},
});
},
},
});
return Festival;
};
//MODELO PARA LUGARESmodule.exports = (sequelize, DataTypes) => {
const Lugar = sequelize.define('Lugar', {
confirmed: {
type: DataTypes.STRING,
allowNull: true,
},
}, {
classMethods: {
associate(models) {
Lugar.hasMany(models.Festival, {
foreignKey: {
name: 'idFestival',
allowNull: true,
},
});
},
},
});
return Lugar;
};

En el código anterior declaramos los modelos que nos ayudarán a obtener un Lugar y sus festivales asociados, así como consultar un Festival y su lugar asociado. Por eso declaramos la relación en ambas direcciones, ya que si no agregamos Lugar.hasMany(models.Festival…), cuando intentemos buscar un lugar y sus festivales asociados, nos responderá con un error parecido a esto:
Festival is not associated to Lugar!

Festivales y Bandas (muchos a muchos)

//MODELO DE FESTIVAL
module.exports = (sequelize, DataTypes) => {
const festival = sequelize.define('Festival', {
confirmed: {
type: DataTypes.STRING,
allowNull: true,
},
}, {
classMethods: {
associate(models) {
Festival.belongsTo(models.Lugar, {
foreignKey: {
name: 'idFestival',
allowNull: true,
},
});
Festival.belongsToMany(models.Banda, {
as: 'Bandas',
through: FestivalBanda,
foreignKey: { name: 'idFestival', allowNull: false }
});
},
},
});
return festival;
};
//MODELO DE BANDA
module.exports = (sequelize, DataTypes) => {
const banda = sequelize.define('Banda', {
confirmed: {
type: DataTypes.STRING,
allowNull: true,
},
}, {
classMethods: {
associate(models) {
Banda.belongsToMany(models.Festival, {
as: 'Bandas',
through: FestivalBanda,
foreignKey: { name: 'idBanda', allowNull: false }
});
},
},
});
return banda;
};

Cuando declaramos asociaciones de muchos a muchos en Sequelize, es necesario agregar el parámetro through, que en este caso es FestivalBanda. Este tendrá el nombre de la tabla JOIN, en la cual nuestros modelos se van asociar y nuevamente declaramos la relación en ambas direcciones.

Consultas

Obtener un festival con su lugar y bandas asociadas

const getInfoFestival = async (req, res) => {
try {
const { id } = req.params;
const festival = await Festival.findOne({
where: {
id
},
include: [
{ model:Banda,
as: 'bandas',
attributes: ['id', 'nombre']
},
{ model: Lugar,
attributes: ['nombre']
},
]
})
res.send(festival);
}
catch () {
res.sendStatus(400);
}
}

Agregar Include es la parte importante de la búsqueda.

La función anterior es para obtener información sobre un festival, basándonos en un parámetro que nos puede llegar a través de un endpoint.

Agregar Include es importante para solicitar cualquier información asociada. Tengamos en cuenta que debemos especificar el alias que le asignamos en la definición del modelo.

Si necesitas información filtrada de algún modelo, basta con agregar Attributes y el campo del modelo, como en el ejemplo anterior agregamos attributes: [‘id’, ‘nombre’].

Resultado de la búsqueda

{
"id": 4,
"name": "Coachella",
"date": "7/20",
"time": "9:00PM",
"bandas": [
{
"id": 5,
"name": "Kendrick Lamar",
},
{
"id": 6,
"name": "Massive Attack",
}
],
"Lugar": {
"id": 1,
"name": "California Park"
}

¡Espera! ¿y si queremos ver más de una asociación?

Entonces:

const getInfoLugar = async (req, res) => {
try {
const { id } = req.params;
const lugar = await Lugar.findOne({
where: {
id
},
attributes: ['id', 'nombre'],
include: [
{ model: Festival,
include: [
{ model: Banda, as: 'bandas',
attributes: ['id', 'nombre']
}
]
}
]
})
res.send(lugar);
}
catch() {
res.sendStatus(400);
}
}

¿No es genial? Basta con anidar un Include para obtener las bandas que tocarán en los festivales que tendrán como sede cierto lugar, por eso es muy importante tener muy bien relacionados los modelos.

Aquí el resultado:

{
{
"id": 1,
"name": "California Park",
"shows": [
{
"id": 1,
"name": "cool show",
"date": "7/20",
"time": "9:00PM",
"bands": [
{
"id": 1,
"name": "Limp Bizkit",
}
]
},
{
"id": 4,
"name": "Coachella",
"date": "7/20",
"time": "9:00PM",
"bands": [
{
"id": 5,
"name": "Kendrick Lamar",
},
{
"id": 6,
"name": "Massive Attack",
}
]
}
]
}
}

Algunas veces es fácil elegir el uso de consultas SQL cuando desconocemos el potencial que un ORM nos ofrece.

Si tu schema tiene demasiadas asociaciones y deseas realizar consultas con combinaciones eficientes pero complicadas, un ORM como Sequelize es un buen compañero.

¡Nos leemos luego!

--

--

Ivan Díaz
Nowports Tech and Product

Senior Software Engineer at Nowports | Program Lead & JavaScript Mentor at Kodemia