Search code examples
node.jssequelize.jsassociations

Sequelize associations with multiple model files


(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".


Solution

  • 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);
    ...