I have two models defined:
db/models/User.js
const Sequelize = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class User extends Sequelize.Model {}
const config = {
sequelize,
modelName: 'user',
underscored: true,
paranoid: true,
});
User.init({
first_name: {
type: DataTypes.STRING,
},
last_name: {
type: DataTypes.STRING,
},
}, config);
return User;
};
db/models/Group.js
const Sequelize = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Group extends Sequelize.Model {}
const config = {
sequelize,
modelName: 'group',
underscored: true,
paranoid: true,
});
Group.init({
name: {
type: DataTypes.STRING,
allowNull: false,
},
}, config);
return Group;
};
I want to create many-to-many association between then (so a group can have many users, and a user can belong to many groups. Here is my db/index.js:
db/index.js
const Sequelize = require('sequelize');
const Logger = require('../config/logger');
const { DB } = require('../config/vars');
const UserModel = require('./models/User');
const GroupModel = require('./models/Group');
const sequelize = new Sequelize(
DB.name,
DB.username,
DB.password,
{
host: DB.host,
dialect: 'postgres',
},
);
const Models = {
User: UserModel(sequelize, Sequelize),
Group: GroupModel(sequelize, Sequelize),
};
const setup = async () => {
// Associations
Models.User.belongsToMany(Models.Group, {
through: 'UserGroups',
as: { singular: 'group', plural: 'groups' },
});
Models.Group.belongsToMany(Models.User, {
through: 'UserGroups',
as: { singuler: 'user', plural: 'users' },
});
await sequelize.sync({ force: true });
};
module.exports = {
...Models,
setup,
Seeders,
Sequelize,
sequelize,
};
When I do User.findAll(), there is no groups attribute returned in the object. How should I properly set up many-to-many relationship here?
User.findAll() will return it associated models by defaults, you need to use include like this :
User.findAll({
include : {
model : Groups
}
})
SIMPLE GUIDE
Related
(Update below)
I'm trying to set up model associations using Sequelize and the .associate method.
I have the setup below but this returns an error:
UnhandledPromiseRejectionWarning: SequelizeEagerLoadingError:
ProductMember is not associated to Product!
What am I doing wrong?
Db setup:
const { Sequelize } = require("sequelize");
const config = require("./db.config.js");
const sequelize = new Sequelize(config[process.env]);
module.exports = sequelize;
db.config.js:
module.exports = {
development: {
database: DB_NAME,
username: DB_USER,
password: DB_PASS,
dialect: DB_DRIVER,
logging: false,
options: {
host: DB_HOST,
port: DB_PORT,
pool: {},
},
},
}
/models/product.js:
const { DataTypes } = require("sequelize");
const sequelize = require("../db");
const Product = sequelize.define(
"Product",
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
name: {
type: DataTypes.STRING(255),
unique: true,
allowNull: false,
},
}
{
tableName: "products",
}
}
Product.associate = (models) => {
Product.belongsToMany(models.User, {
through: models.ProductMember,
foreignKey: "product_id",
otherKey: "user_id",
});
Product.hasMany(models.ProductMember, {
foreignKey: "product_id",
allowNull: false,
});
};
module.exports = sequelize.model("Product", Product);
models/user.js:
const { DataTypes } = require("sequelize");
const sequelize = require("../db");
const User = sequelize.define(
"User",
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
email: {
type: DataTypes.STRING(255),
allowNull: false,
unique: true,
isEmail: true,
},
username: {
type: DataTypes.STRING(15),
allowNull: false,
unique: true,
},
},
{
tableName: "users",
}
);
User.associate = (models) => {
User.belongsToMany(models.Product, {
through: models.ProductMember,
foreignKey: "user_id",
otherKey: "product_id",
});
User.hasMany(models.ProductMember, {
foreignKey: "user_id",
allowNull: false,
});
User.belongsToMany(models.Product, {
through: models.UserFavouriteProducts,
foreignKey: "user_id",
otherKey: "product_id",
});
}
module.exports = sequelize.model("User", User);
models/productMember.js:
const { DataTypes } = require("sequelize");
const sequelize = require("../db");
const ProductMember = sequelize.define(
"ProductMember",
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
isAdmin: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
},
{
tableName: "product_members",
}
);
ProductMember.associate = (models) => {
ProductMember.belongsTo(models.User);
ProductMember.belongsTo(models.Product);
};
module.exports = sequelize.model("ProductMember", ProductMember);
Update: Based on this post I updated the Db setup file to:
const fs = require('fs');
const path = require('path');
var basename = path.basename(module.filename);
const models = path.join(__dirname, '../models');
const db = {};
const Sequelize = require("sequelize");
const config = require("./db.config.js");
const sequelize = new Sequelize(config[process.env]);
fs
.readdirSync(models)
.filter(function (file) {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
.forEach(function (file) {
var model = require(path.join(models, file))(
sequelize,
Sequelize.DataTypes
);
db[model.name] = model;
})
Object.keys(db).forEach(function (modelName) {
if (db[modelName].associate) {
db[modelName].associate(db);
}
})
db.Sequelize = Sequelize;
db.sequelize = sequelize;
module.exports = db;
Model file:
module.exports = (sequelize, Sequelize) => {
const Coupon = db.sequelize.define(
//... continue as it was
return Coupon;
}
So for the model file:
Wrapped it inside module.exports = (sequelize, Sequelize) => { }
return Coupon at the end
removed const sequelize = require("../db");
New problem: But with this new setup, Sequelize-related controller methods no longer work... For example for a controller file:
const User = require("../models/user");
const ensureLoggedIn = async (req, res, next) => {
...
const user = await User.findByPk(id);
it produces the error:
User.findByPk is not a function
I've tried adding const sequelize = require("../db"); to the controller file and then const user = await sequelize.User.findByPk(id); but that produced the same error.
I've tried adding const db = require("../db"); to the controller file and then const user = await db.sequelize.User.findByPk(id); but that produced the error "Cannot read property 'findByPk' of undefined".
First, you don't need to call sequelize.model to get a model that registered in Sequelize, you can just export a return value from sequelize.define:
const Product = sequelize.define(
"Product",
...
module.exports = Product;
Second, it seems you didn't call associate methods of all registered models.
Look at my answer here to get an idea of how to do it.
Update: to use models if you defined them like in my answer you need to access them like this:
const { sequelize } = require("../db");
const user = await sequelize.models.User.findByPk(id);
...
I'm having a trouble where I couldn't access the relationship data table from NodeJS PostgreSQL and Express.
First of all here is my simple table :
As you can see, my "jenis" table has association with "kategori" table, one jenis can have multiple kategori, and one kategori should only contain one jenis.
Here is my migration on Postgres Sequelize :
jenis-migration.js
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Umkm_Jenis", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
nama: {
type: Sequelize.STRING,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable("Umkm_Jenis");
},
};
kategori-migration.js
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Umkm_Kategori", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
jenis_id: {
type: Sequelize.INTEGER,
allowNull: false,
},
nama: {
type: Sequelize.STRING,
allowNull: false,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable("Umkm_Kategori");
},
};
jenis.model.js
"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class Umkm_Jenis extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
this.hasMany(models.Umkm_Kategori, {
foreignKey: "jenis_id",
as: "umkm_kategori",
});
}
}
Umkm_Jenis.init(
{
nama: DataTypes.STRING,
},
{
sequelize,
modelName: "Umkm_Jenis",
freezeTableName: true,
}
);
return Umkm_Jenis;
};
kategori-model.js
"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class Umkm_Kategori extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
this.belongsTo(models.Umkm_Jenis, {
foreignKey: "jenis_id",
as: "umkm_jenis",
});
}
}
Umkm_Kategori.init(
{
jenis_id: DataTypes.INTEGER,
nama: DataTypes.STRING,
},
{
sequelize,
modelName: "Umkm_Kategori",
freezeTableName: true,
}
);
return Umkm_Kategori;
};
I have created some seeder data on postgres for both jenis and kategori table.
Now I want my getAllKategori API to actually include the value from "jenis" table using the relationship that I have made. Below is my apiController for kategori :
exports.findAllKategori = async (req, res) => {
//check if theres a filter applied
const nama = req.query.nama;
const condition = nama ? { nama: { [Op.iLike]: `%${nama}%` } } : null;
try {
const response = Kategori.findAll({
where: condition,
include: [{ model: db.jenis, as: "umkm_jenis", required: false }],
});
const data = await response;
return res.status(200).send(data);
} catch (error) {
console.log(error);
return res.status(500).send(error);
}
};
But when I tried to hit the API from POSTMAN, it shows SequelizeEagerLoadingError.
If I commented this line on kategoriController :
include: [{ model: db.jenis, as: "umkm_jenis", required: false }],
and then try to hit the api once more, it successfully shows all my data.
I have already read some similar question in this forum but none of it seems to work.
Thank you for taking your time!
UPDATE
Below is the models/index.js file
"use strict";
const fs = require("fs");
const path = require("path");
const Sequelize = require("sequelize");
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || "development";
const config = require(__dirname + "/../config/config.json")[env];
const db = {};
let sequelize;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(
config.database,
config.username,
config.password,
config
);
}
fs.readdirSync(__dirname)
.filter((file) => {
return (
file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".js"
);
})
.forEach((file) => {
const model = require(path.join(__dirname, file))(
sequelize,
Sequelize.DataTypes
);
db[model.name] = model;
});
Object.keys(db).forEach((modelName) => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
db.kategori = require("./umkm_kategori")(sequelize, Sequelize);
db.jenis = require("./umkm_jenis")(sequelize, Sequelize);
db.produk = require("./umkm_produk")(sequelize, Sequelize);
db.review = require("./umkm_review")(sequelize, Sequelize);
module.exports = db;
console.log(error)
You already registered all models and associations with this code pieces:
const model = require(path.join(__dirname, file))(
sequelize,
Sequelize.DataTypes
);
db[model.name] = model;
and
Object.keys(db).forEach((modelName) => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
so this code is registering models once again, just remove it and use model names defined above.
db.kategori = require("./umkm_kategori")(sequelize, Sequelize);
db.jenis = require("./umkm_jenis")(sequelize, Sequelize);
db.produk = require("./umkm_produk")(sequelize, Sequelize);
db.review = require("./umkm_review")(sequelize, Sequelize);
I'm trying to add 'roles' to users - each user can have multiple roles, and roles aren't limited to a number of users - they can be assigned to as many users as is needed. I followed a few tutorials, read through quite a few stackoverflow questions/answers, and still can't seem to get this to work.
user.model.js
'use strict';
const { Model } = require('sequelize');
const PROTECTED_ATTRIBUTES = ['password'];
module.exports = (sequelize, DataTypes) => {
class User extends Model {
toJSON() {
// hide protected fields
const attributes = { ...this.get() };
// eslint-disable-next-line no-restricted-syntax
for (const a of PROTECTED_ATTRIBUTES) {
delete attributes[a];
}
return attributes;
}
static associate(models) {
User.belongsToMany(models.Role, {
through: 'UserRoles',
as: 'roles',
foreignKey: 'roleId'
});
}
}
User.init({
name: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false
},
lastLoginAt: {
type: DataTypes.DATE,
allowNull: true
},
lastIPAddress: {
type: DataTypes.STRING,
allowNull: true
}
}, {
sequelize,
modelName: 'User',
paranoid: true,
tableName: 'users'
});
return User;
};
role.model.js
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Role extends Model {
static associate(models) {
Role.belongsToMany(models.User, {
through: 'UserRoles',
as: 'users',
foreignKey: 'userId'
});
}
};
Role.init({
roleName: DataTypes.STRING
}, {
sequelize,
modelName: 'Role',
});
return Role;
};
userroles.model.js
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class UserRoles extends Model {
static associate(models) {
// define association here
}
};
UserRoles.init({
userId: DataTypes.INTEGER,
roleId: DataTypes.INTEGER
}, {
sequelize,
modelName: 'UserRoles',
});
return UserRoles;
};
user.controller.js
async function getUsers(req, res, next) {
const name = req.query.name;
const condition = name ? { name: { [Op.like]: `%${name}%` } } : null;
User.findAll({ where: condition, include: { model: Role, as: 'roles'} })
.then(data => {
res.send(data);
})
.catch(err => {
const error = new createError(500, 'Some error occurred while retrieving users.');
return next(error);
});
}
The query returns a 'roles' array, but nothing is inside it even though the UserRoles table has an entry assigned to an existing user and existing role.
The query it generates, in case that helps:
SELECT "User"."id", "User"."name", "User"."email", "User"."password", "User"."lastLoginAt", "User"."lastIPAddress", "User"."createdAt", "User"."updatedAt", "User"."deletedAt", "roles"."id" AS "roles.id", "rol
es"."roleName" AS "roles.roleName", "roles"."createdAt" AS "roles.createdAt", "roles"."updatedAt" AS "roles.updatedAt", "roles->UserRoles"."userId" AS "roles.UserRoles.userId", "roles->UserRoles"."roleId" AS "roles.UserRoles.role
Id", "roles->UserRoles"."createdAt" AS "roles.UserRoles.createdAt", "roles->UserRoles"."updatedAt" AS "roles.UserRoles.updatedAt" FROM "users" AS "User" LEFT OUTER JOIN ( "UserRoles" AS "roles->UserRoles" INNER JOIN "role" AS "ro
les" ON "roles"."id" = "roles->UserRoles"."roleId") ON "User"."id" = "roles->UserRoles"."roleId" WHERE ("User"."deletedAt" IS NULL);
role.model.js
'use strict';
const {Model} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Role extends Model {
static associate(models) {
//...
Role.belongsToMany(models.UserRoles, {
as: 'userRoles',
foreignKey: 'roleId',
});
}
}
// ...
return Role;
};
User.findAll({where: condition, include: {model: Role, as: 'roles', include: ['userRoles']}})
^^^^^^^^^^^^^^^^^^^^^^
I have a Product model:
module.exports = (sequelize, Sequelize) => {
const Product = sequelize.define("product", {
SKU: {
type: Sequelize.STRING,
},
name: {
type: Sequelize.STRING,
},
description: {
type: Sequelize.TEXT,
},
categoryID: {
type: Sequelize.INTEGER,
}
});
Product.associate = function (models) {
Product.hasMany(models.Stock, { foreignKey: 'productID', as: 'stocks' })
};
return Product;
}
Which has a hasMany relation with the Stock model:
module.exports = (sequelize, DataTypes) => {
const Stock = sequelize.define("stock", {
note: DataTypes.STRING,
})
Stock.associate = function (models) {
Stock.belongsTo(models.Product)
};
return Stock;
}
When trying to query the Products, I get the following error:
exports.getAllAvailableProducts = (req, res) => {
const queryParams = req.query;
Product.findAll({
include: ['stocks'],
where: queryParams
}).then((products) => {
res.status(200).send(products);
})
};
(node:58108) UnhandledPromiseRejectionWarning: Error: Association with alias "stocks" does not exist on product
Am i doing something wrong here? I don't see the FK appearing in my database either
module.exports = (sequelize, Sequelize) => {
const Product = sequelize.define("product", {
SKU: {
type: Sequelize.STRING,
},
name: {
type: Sequelize.STRING,
},
description: {
type: Sequelize.TEXT,
},
categoryID: {
type: Sequelize.INTEGER,
}
});
Product.associate = function (models) {
// models.stock is correct
Product.hasMany(models.stock, { foreignKey: 'productID', as: 'stocks' })
};
return Product;
}
module.exports = (sequelize, DataTypes) => {
const Stock = sequelize.define("stock", {
note: DataTypes.STRING,
})
Stock.associate = function (models) {
// models.product is correct
//Stock.belongsTo(models.product)
Stock.belongsTo(models.product, {
foreignKey: 'productId',
as: 'product'
})
};
return Stock;
}
models/index.js
const dbConfig = require("../config/db.config.js");
const Sequelize = require("sequelize");
const sequelize = new Sequelize(dbConfig.DB, dbConfig.USER, dbConfig.PASSWORD, {
host: dbConfig.HOST,
dialect: dbConfig.dialect,
operatorsAliases: false,
pool: {
max: dbConfig.pool.max,
min: dbConfig.pool.min,
acquire: dbConfig.pool.acquire,
idle: dbConfig.pool.idle
}
});
const db = {};
db.Sequelize = Sequelize;
db.sequelize = sequelize;
db.user = require("./user.model.js")(sequelize, Sequelize);
db.category = require("./category.model.js")(sequelize, Sequelize);
db.product = require("./product.model.js")(sequelize, Sequelize);
db.orderline = require("./orderline.model.js")(sequelize, Sequelize);
db.order = require("./order.model.js")(sequelize, Sequelize);
db.stock = require("./stock.model.js")(sequelize, Sequelize);
// we need call associate function
db.stock.associate(db)
db.product.associate(db);
module.exports = db;
Product.findAll({
include: ['stocks'],
where: queryParams
}).then((products) => {
res.status(200).send(products);
})
I have two Sequelize.js models what are connected by many-to-many relation.
User:
module.exports = (sequelize, DataTypes) => {
const model = sequelize.define('User', {
email: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
password: {
type: DataTypes.CHAR,
length: 60,
allowNull: false
}
}
});
model.associate = models => {
model.belongsToMany(models.Role, {
hooks: true,
through: 'user_roles'
})
};
return model;
};
Role:
module.exports = (sequelize, DataTypes) => {
const model = sequelize.define('Role',
{
name: {
type: DataTypes.STRING,
unique: false,
allowNull: false
}
}
);
model.associate = models => {
model.belongsToMany(models.User, {
hooks: true,
through: 'user_roles'
});
};
return model;
};
Table user_roles is created automatically.
How to set default role what will be inserted into user_roles while creating new User? Is there way to define it in models definition or I am just supposed to create User new user and then create relation in one transaction?
First you need to create a model for the user_roles after creating that you have to set a hook inside your user model that will automatically insert roles in to your user_roles table.
This is how you can do it.
For Example :
User Model
const { hooks } = require('./user-role.hook');
module.exports = (sequelize, DataTypes) => {
const model = sequelize.define('User', {
email: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
password: {
type: DataTypes.CHAR,
length: 60,
allowNull: false
}
}, { hooks });
model.associate = models => {
model.belongsToMany(models.Role, {
hooks: true,
through: 'user_roles'
})
};
return model;
};
Hook file
exports.hooks = {
afterCreate: (User, payload) => {
AddUserRole(User, payload);
}
}
function AddUserRole(User, payload) {
let InsertArr = {
user_id: User._id,
role_id: 1 // pass default role id
}
model.UserRole.create(InsertArr);
}