0

I have two models Category and Product. The product can have one Category, Category can have many products. This is a One-to-Many relationship.

product.js file:

import { DataTypes, Model } from 'sequelize';
import connection from '../connection';
import Category from '@/database/models/category';

const initProduct = (sequelize, Types) => {
    class Product extends Model {
    }

    Product.init(
        {
            name: Types.STRING,
            description: Types.STRING,
            price: Types.NUMBER,
            order: Types.NUMBER,
            categoryId: Types.INTEGER,
            showOnMenu: Types.BOOLEAN
        },
        {
            sequelize,
            modelName: 'Product',
            tableName: 'Product',
            createdAt: 'created_at',
            updatedAt: 'updated_at'
        }
    );


    Product.associate = () => {
        Product.belongsTo(Category);
    };

    return Product;
};

export default initProduct(connection, DataTypes);

migration-product.js

module.exports = {
    async up(queryInterface, Sequelize) {
        await queryInterface.createTable('Product', {
            id: {
                allowNull: false,
                autoIncrement: true,
                primaryKey: true,
                type: Sequelize.INTEGER
            },
            name: {
                type: Sequelize.STRING
            },
            description: {
                type: Sequelize.STRING
            },
            categoryId: {
                type: Sequelize.INTEGER,
                allowNull: false,
                references: {
                    model: 'Category',
                    key: 'id'
                }
            },
            price: {
                type: Sequelize.NUMERIC
            },
            order: {
                type: Sequelize.INTEGER
            },
            showOnMenu: {
                type: Sequelize.BOOLEAN
            },
            created_at: {
                allowNull: false,
                type: Sequelize.DATE,
                defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
            },
            updated_at: {
                allowNull: false,
                type: Sequelize.DATE,
                defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
            }
        });
    },
    async down(queryInterface) {
        await queryInterface.dropTable('Product');
    }
};

category.js

import { DataTypes, Model } from 'sequelize';
import connection from '../connection';
import Product from '@/database/models/product';

const initCategory = (sequelize, Types) => {
    class Category extends Model {
    }

    Category.init(
        {
            name: Types.STRING,
            description: Types.STRING,
            order: Types.NUMBER,
            showOnMenu: Types.BOOLEAN
        },
        {
            sequelize,
            modelName: 'Category',
            tableName: 'Category',
            createdAt: 'created_at',
            updatedAt: 'updated_at'
        }
    );

    Category.associate = () => {
        Category.hasMany(Product);
    };

    return Category;
};

export default initCategory(connection, DataTypes);

migration-category.js

module.exports = {
    async up(queryInterface, Sequelize) {
        await queryInterface.createTable('Category', {
            id: {
                allowNull: false,
                autoIncrement: true,
                primaryKey: true,
                type: Sequelize.INTEGER
            },
            name: {
                type: Sequelize.STRING
            },
            description: {
                type: Sequelize.STRING
            },
            order: {
                type: Sequelize.INTEGER
            },
            showOnMenu: {
                type: Sequelize.BOOLEAN
            },
            created_at: {
                allowNull: false,
                type: Sequelize.DATE,
                defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
            },
            updated_at: {
                allowNull: false,
                type: Sequelize.DATE,
                defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
            }
        });
    },
    async down(queryInterface) {
        await queryInterface.dropTable('Category');
    }
};

Here I am connecting with Sequelize

connection.js

import Sequelize from 'sequelize';
import config from './config/config.mjs';

let sequelize;
if (process.env.NODE_ENV === 'production') {
    sequelize = new Sequelize(config.production);
} else if (process.env.NODE_ENV === 'staging') {
    sequelize = new Sequelize(config.staging);
} else if (process.env.NODE_ENV === 'test') {
    sequelize = new Sequelize(config.test);
} else {
    sequelize = new Sequelize(config.development);
}

const connection = sequelize;

export default connection;

I'm trying to get all the products and include category name: In SQL it will be like this:

SELECT p.id, p.name, p.price, c.name AS categoryName FROM Product p
INNER JOIN Category c ON p.categoryId = c.id;

In sequelize I'm trying like this:

const products = await Product.findAll({
    include: {
        model: Category,
        attributes: ['id', 'name']
    },
    attributes: ['id', 'name', 'price']
});

I'm getting this error

EagerLoadingError [SequelizeEagerLoadingError]: Category is not associated to Product!

I'm not sure how I can solve this error. The tables are created OK, I can see FK in the Product table

Product_ibfk_1 -> (categoryId) -> Category (id)

KiKo
  • 1,668
  • 7
  • 27
  • 56
  • Did you call `associate` for all models after their registration? – Anatoly Jul 11 '23 at 16:32
  • Please show your Product model. When you do `Product.findAll`, you need `associate` in Product model. In Sequelize, association is not bi-directional. If you have `Category.hasMany(Product)`, this will create FK in your database, however, it won't still allow you to query from Product including Category without `Product.belongsTo(Category)` – Emma Jul 11 '23 at 20:32
  • @Anatoly I have only this: Product.associate = () => { Product.belongsTo(Category); }; on product.js and Category.associate = () => { Category.hasMany(Product); }; on category.js Im not sure if this is enough – KiKo Jul 13 '23 at 07:35
  • @Emma I have the product model. It's first file on the question: product.js – KiKo Jul 13 '23 at 07:40

1 Answers1

0

You need to call associate for all models in somewhere like connection.js. Also it's not a good idea to cross-reference models in model modules. Look at my other answer here to get an idea how to define and register models and their associations.

Anatoly
  • 20,799
  • 3
  • 28
  • 42