How to create a Many to Many association in sequelize and nodejs - node.js

I am trying to create a many to many relationship between user table and role table through userroles table.
After table creation the db looks just fine, I tried pretty much everything I found on the sequelize documentation and previous answers here, nothing seems to work.
I am getting this error: EagerLoadingError [SequelizeEagerLoadingError]: UserRoles is not associated to User!
Any idea of what am I doing wrong ? please help!
class User extends Model {
static associate(models) {
User.belongsToMany(models.Role, {
foreignKey: "user_id",
through:'UserRoles',
as:"users"
});
}
}
User.init(
{
user_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
unique: true,
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
phone: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
sequelize,
modelName: "User",
}
);
class Role extends Model {
static associate(models) {
Role.belongsToMany(models.User, {
foreignKey: "role_id",
through:'UserRoles',
as:"roles"
});
}
}
Role.init(
{
role_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
unique:true
},
role_name: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
role_desc: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
sequelize,
modelName: "Role",
}
);
class UserRoles extends Model {
static associate(models) {
}
}
UserRoles.init(
{
userroles_id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
// user_id: {
// type: DataTypes.UUID,
// defaultValue: DataTypes.UUIDV4,
// },
// role_id: {
// type: DataTypes.UUID,
// defaultValue: DataTypes.UUIDV4,
// },
},
{
sequelize,
modelName: "UserRoles",
}
);
const signup = (req, res) => {
console.log(req.body);
console.log("signup entry");
if (
!req.body.role ||
!req.body.email ||
!req.body.password ||
!req.body.name ||
!req.body.phone
) {
res.status(400).send({
msg: "Please pass role, email, password and name.",
});
} else {
sequelize.models.User.findOne({
where: {
email: req.body.email,
},
})
.then((duplicateemailfound) => {
if (duplicateemailfound) {
console.log(duplicateemailfound);
return res.status(400).json({
success: false,
message: "Email already registered",
});
} else {
let userRole = req.body.role.toLowerCase();
console.log("userRole:", userRole);
sequelize.models.Role.findOne({
where: {
role_name: userRole,
},
})
.then((foundRole) => {
// console.log(foundRole);
if (foundRole == null) {
return res.status(400).json({
success: false,
role: "null or not found",
});
}
// console.log("foundRole", foundRole); // .role_id
let addUser = {
email: req.body.email,
password: req.body.password,
name: req.body.name,
phone: req.body.phone,
role_id: foundRole.role_id,
};
sequelize.models.User.create(addUser, {
include: [{ model: sequelize.models.UserRoles }],
})
.then((newUser) => {
console.log("new user", newUser);
return res.status(201).json({
success: true,
newuser: newUser,
});
})
.catch((error) => {
console.log(error);
res.status(400).json({
success: false,
// message: "Duplicate Email was Found",
error: error.errors[0].message,
error: error,
});
});
})
.catch((error) => {
console.log(error);
res.status(400).json({
error: error,
msg: "bbb",
});
});
}
})
.catch((err) => {
console.log(err);
});
}
};

You create some a class for each Model and extend them with Model class of sequelize, this if fine.
Now, you define a static method inside the class named associate(model) where you define the rule for that class. This is fine because you used static which is required here to since it will be a property of the class, not of an object.
Then you call the initialize method (a in-built method of class Model). In the same way you need to call your defined associate.
Here is a problem, because in the structure that you have now, you can't call that method in it's own class file, becuase you need the other Model to pass it as parameter.
So there are 2 solutions:
Import your User model inside Role model file and call the static method, like this:
const User = require('User')
class Role extends Model {
static associate(model) {
Role.belongsToMany(model, {
foreignKey: "role_id",
through:'UserRoles',
as:"roles"
});
}
}
Role.init(
{
role_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
unique:true
},
role_name: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
role_desc: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
sequelize,
modelName: "Role",
}
);
Role.associate(User);
This will use your User model to pass it to the static method and finally to run the belongsToMany
Second solution would be to create an index file for your Models, where you import both of them and you can simply run that belongsToMany there, and then simply import that index file in the main file of your application, something like this:
User.js
const index = require('./../index.js');
const Sequelize = require('sequelize');
const Model = Sequelize.Model;
const sequelize = index.sequelize;
class User extends Model{}
User.init({
username: {
type: Sequelize.STRING,
allowNull: false,
unique: true
},
password: {
type: Sequelize.STRING,
allowNull: false
},
role: {
type: Sequelize.STRING,
allowNull: false
}
},{
sequelize,
modelName: 'user'
});
module.exports = {
User: User
}
Role.js will look the same but with your own model.
and index.js would look like this:
const Sequelize = require('sequelize');
const sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USERNAME, process.env.DB_PASSWORD, {
host: process.env.DB_HOST,
dialect: process.env.DB_DIALECT
});
exports.sequelize = sequelize;
const user = require('./models/User');
const role= require('./models/Role');
role.belongsToMany(user, {
foreignKey: "role_id",
through:'UserRoles',
as:"roles"
});
sequelize.sync(user);
sequelize.sync(role);
exports.db = {
user: user,
role: role
}

Related

can not get userID in sequelize?

this is user model
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class User 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({ Kyc }) {
// define association here
this.hasOne(Kyc, { foreignKey: "userID" });
}
}
User.init(
{
uuid: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
},
firstname: {
type: DataTypes.STRING,
allowNull: false,
},
lastname: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
allowNull: false,
},
role: {
type: DataTypes.STRING,
defaultValue: "user",
allowNull: false,
validate: {
roles(value) {
const rolesArray = ["user", "admin"];
if (!rolesArray.includes(value)) {
throw new Error('plese enter valid role "user or admin"');
}
},
},
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
sequelize,
modelName: "User",
}
);
return User;
};
this is kyc model
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class Kyc 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({ User }) {
// define association here
this.belongsTo(User, { foreignKey: "userID", as: "user" });
}
}
Kyc.init(
{
uuid: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
},
docimg: {
type: DataTypes.STRING,
allowNull: false,
},
details: {
type: DataTypes.STRING,
allowNull: false,
},
status: {
type: DataTypes.STRING,
allowNull: false,
},
userID: {
type: DataTypes.INTEGER,
allowNull: false,
},
},
{
sequelize,
modelName: "Kyc",
}
);
return Kyc;
};
kyc middlware
const verifyKyc = async (req, res, next) => {
// check that user has posted or not if yes then give error
const user = await User.findByPk(req.user.id);
const kyc = await Kyc.findOne({
userID: req.user.id,
});
console.log(user.id);
console.log(kyc);
if (user.id === kyc) {
}
next();
};
Error
Executing (default): SELECT "id", "uuid", "firstname", "lastname", "email", "role", "password", "createdAt", "updatedAt" FROM "Users" AS "User" WHERE "User"."id" = 1;
(sequelize) Warning: Model attributes (userID) passed into finder method options of model Kyc, but the options.where object is empty. Did you forget to use options.where?
Executing (default): SELECT "id", "uuid", "docimg", "details", "status", "userID", "createdAt", "updatedAt" FROM "Kycs" AS "Kyc" LIMIT 1;
1
Kyc {
dataValues: {
id: 117,
uuid: '99461f78-4781-42cc-a01f-b6541fda849d',
docimg: 'admin.png',
details: 'KSAPK0550P',
status: 'pending',
userID: 1,
createdAt: 2022-06-04T10:59:21.039Z,
updatedAt: 2022-06-04T10:59:21.039Z
_previousDataValues: {
id: 117,
uuid: '99461f78-4781-42cc-a01f-b6541fda849d',
docimg: 'admin.png',
details: 'KSAPK0550P',
status: 'pending',
userID: 1,
createdAt: 2022-06-04T10:59:21.039Z,
updatedAt: 2022-06-04T10:59:21.039Z
isNewRecord: false
}
i am tring to print userID but giving me this error
thank you for your help
You forgot to wrap your condition into where option:
const kyc = await Kyc.findOne({
where: {
userID: req.user.id,
}
});

Node.js sequelize Create an object although the association fails

I am using sequelize on node.js.
My model object for Asset:
module.exports =
class Asset extends Model {
static init(sequelize) {
return super.init(
{
AssetID: {
field: "asset_id",
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
AssetName: {
field: "asset_name",
type: DataTypes.STRING(40),
allowNull: false
},
SKU: {
field: "sku",
type: DataTypes.STRING(30)
}
},
Object.assign({
sequelize,
tableName: "assets",
})
);
}
static associate(models) {
Asset.Listings = this.hasMany(models.Listing,
{
as: 'Listings',
foreignKey: {name: 'AssetID', field: 'asset_id'},
sourceKey: 'AssetID'
})
}
};
And the listing definition is:
module.exports =
class Listing extends Model {
static init(sequelize) {
return super.init(
{
ListingID: {
field: "listing_id",
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
IsPreferred: {
field: "isPreferred",
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
Price: {
field: "price",
type: DataTypes.DOUBLE(5,2),
allowNull: false,
validate: { min: 0 }
},
ListingType: {
field: "listing_type",
type: DataTypes.STRING(20),
allowNull: false,
defaultValue: "Buy",
validate: {
isIn: [['Buy', 'Rent']]
}
}
},
Object.assign({
sequelize,
tableName: "listings",
})
);
}
this is my create code:
await Asset.create(newRecord, {include: [{ association: Asset.Listings } ]})
.then(dbRecord => {
logger.debug(`New Asset created`);
return dbRecord;
})
.catch(err => {
logger.warn(`Asset could not be created. ${JSON.stringify(newRecord)}`, err);
throw createServiceError(`Create Asset`, err);
})
Now...my problem is that when I create a new Asset record with the listing association, in case that the creation of the listing fails (for example on the type validation, say I put "abc"), the listing will not be created (as the value is invalid) but the Asset is created.
I would expect that the entire transaction will be reverted.
It is a bit more strange because the create call ends up in the catch block.
Any ideas?
Thanks,
Asaf
You can use transaction.
Sorry for formating I'm giving answer from phone.
sequelize.transaction(transaction => {
Asset.create(newRecord, {include: [{ association: Asset.Listings } ], transaction})
})
.then(function() {
console.log('success');
})
.catch(function(err) {
console.log(err);
})
})

SEQUELIZE: belongsTo called with something that's not a subclass of Sequelize.Model

I'm newbie with sequelize and I'm trying to assiacite two tables. Users and Projects. I have defined a model for each one called UserModel and ProjectModel and when I try to associate projects with user I've got this error:
belongsTo called with something that's not a subclass of
Sequelize.Model
These are the files where I define each model:
user.js
module.exports = (sequelize, DataType) => {
const UserModel = sequelize.define("user", {
id: {
type: DataType.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
email: {
type: DataType.STRING,
allowNull: false,
isEmail: {
msg: "The format of the e-mail is not correct"
},
validate: {
notNull: {
msg: "E-mail cannot be empty"
}
}
},
name: {
type: DataType.STRING,
is: /^[a-zA-Z ]+$/i,
allowNull: false,
validate: {
notNull: {
msg: "Name cannot be empty"
}
}
},
surname: {
type: DataType.STRING,
is: /^[a-zA-Z ]+$/i,
allowNull: false,
validate: {
notNull: {
msg: "Surname cannot be empty"
}
}
}
});
UserModel.associate = (models) => {
UserModel.hasMany(models.ProjectModel, {
foreignKey: "userID"
})
}
return UserModel;
};
project.js
module.exports = (sequelize, DataType) => {
const ProjectModel = sequelize.define("project", {
id: {
type: DataType.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
name: {
type: DataType.STRING,
is: /^[a-zA-Z ]+$/i,
allowNull: false,
validate: {
notNull: {
msg: "Name cannot be empty"
}
}
},
body: {
type: DataType.TEXT,
allowNull: false,
validate: {
notNull: {
msg: "Body cannot be empty"
}
}
},
status: {
type: DataType.ENUM("active", "inactive", "declined", "completed"),
allowNull: false,
validate: {
notNull: {
msg: "Status cannot be empty"
}
}
},
userID: {
type: DataType.INTEGER,
allowNull: false,
validate: {
notNull: {
msg: "userID cannot be empty"
}
},
references: {
model: UserModel,
key: "id"
}
}
});
ProjectModel.associate = (models) => {
ProjectModel.belongsTo(models.UserModel, {
foreignKey: "userID"
});
}
ProjectModel.belongsTo(UserModel, {
foreignKey: "userID"
});
return ProjectModel;
}
What am I doing wrong?
Finally, I have found the solution to my problem. In this association, for example:
ProjectModel.associate = (models) => {
ProjectModel.belongsTo(models.UserModel, {
foreignKey: "userID"
});
}
With the variable models.UserModel I was making reference to the variable which is returned by the model. Instead of this, you have to write the name of the table. So, in my case the correct function is:
ProjectModel.associate = (models) => {
ProjectModel.belongsTo(**models.users**, {
foreignKey: "userID"
});
}
Don't import model in another model file. like if you wrote A.belongsTo(B) file,then don't import A file into B file.

Sequelize Node.js Model not associated to model

I have two models in my Node JS application, Users and Companies. When I try to use the include function when getting the companies, I get the error:
message: "users is not associated to companies!"
This is the user model
import { Sequelize, DataTypes } from 'sequelize';
import { Application } from '../declarations';
export default function (app: Application) {
const sequelizeClient: Sequelize = app.get('sequelizeClient');
const users = sequelizeClient.define('users', {
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: true
},
role: {
type: DataTypes.STRING,
allowNull: true,
},
language: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: 'en',
},
firstName: {
type: DataTypes.STRING,
allowNull: false
},
lastName: {
type: DataTypes.STRING,
allowNull: false,
},
}, {
underscored: true,
hooks: {
beforeCount(options: any) {
options.raw = true;
}
}
});
// eslint-disable-next-line no-unused-vars
(users as any).associate = function (models: any) {
(users as any).belongsTo(models.companies, {
foreignKey: 'companyId',
allowNull: true,
onDelete: "CASCADE",
});
};
return users;
}
and the companies model:
import { Sequelize, DataTypes, Model } from 'sequelize';
import { Application } from '../declarations';
export default function (app: Application) {
const sequelizeClient: Sequelize = app.get('sequelizeClient');
const companies = sequelizeClient.define('companies', {
name: {
type: DataTypes.STRING,
allowNull: false
},
phone: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: '',
},
email: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: '',
},
active: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true,
},
}, {
underscored: true,
hooks: {
beforeCount(options: any) {
options.raw = true;
}
}
});
// eslint-disable-next-line no-unused-vars
(companies as any).associate = function (models: any) {
(companies as any).hasMany(models.users);
(companies as any).hasMany(models.patients);
};
return companies;
}
And the include function
function getSuperAdmin() {
return (context: HookContext) => {
const sequelizeClient = context.app.get("sequelizeClient");
context.params.sequelize = {
include: [
{
model: sequelizeClient.models.users,
as: 'users',
required: false,
where: {
'$users.role$': 'super_admin'
}
}
],
};
return context;
}
}
When I include the companies model in the user query, it works correctly. But not the other way around. Any ideas?
Thanks

Create a composite index with associated data?

I have a User-table and a userAttributes table. Every user can only have one instance of each userAttribute, so I would like to create a composite unique index for the columns name and userId(That is created by userAttributes.belongsTo.) How can this be done?
UserAttribute = sequelize.define('userAttributes', {
name: {
type: Sequelize.STRING,
allowNull: false,
unique: 'nameIndex',
validate: {
isIn: [['phone', 'name','driverRequest']],
}
},
value: {
type: Sequelize.STRING,
allowNull: false
},
});
User.hasMany(userAttributes, {unique: 'nameIndex'});
userAttributes.belongsTo(User, {unique: 'nameIndex'});
I tride adding the unique nameIndex with no success, it seems to only apply to the name-column.
var Sequelize = require('sequelize');
var sequelize = new Sequelize('<yourDatabaseName>', '<yourUserName>', '<yourPassword', {
host: '<ip>'
});
var User = sequelize.define('user', {
id: {
type: Sequelize.STRING,
allowNull: false,
primaryKey: true
},
});
var UserAttribute = sequelize.define('userattribute', {
userId: {
type: Sequelize.STRING,
allowNull: false,
unique: 'compositeIndex',
references: {
model: User,
key: "id"
}
},
name: {
type: Sequelize.STRING,
allowNull: false,
unique: 'compositeIndex'
}
});
User.hasOne(UserAttribute, {
as: "UserAttribute"
})
UserAttribute.belongsTo(User, {
foreignKey: "userId",
as: 'UserId'
})
sequelize.sync({
// use force to delete tables before generating them
force: true
}).then(function() {
console.log('tables have been created');
return User.create({
id: 'randomId1'
});
})
.then(function() {
console.log('tables have been created');
return User.create({
id: 'randomId2'
});
})
.then(function() {
return UserAttribute.create({
userId: 'randomId1',
name: 'name1'
});
})
.then(function() {
return UserAttribute.create({
userId: 'randomId2',
name: 'name1'
});
})
// // generates Validation Error
// .then(function() {
// return UserAttribute.create({
// userId: 'randomId1',
// name: 'name1'
// });
// })

Resources