'How to create a Many to Many association in sequelize and nodejs

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);
      });
  }
};


Solution 1:[1]

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:

  1. 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

  1. 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
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1