Sequelize (PostgresSQL) many to many chat implementation - node.js

I've been trying to create a chat app with Node JS and Sequelize. Now i'm stuck at a problem of creating a query to find a conversation that has my id and user's id(the one i'm trying to text). So the thing i'm trying to do is send a post request with and id of a user i'm sending a message to, then i look through my Conversation model and check if that conversation has my id and id of the user i'm texting to.
My models are associated through Many to Many relationship. So the main objective is to find a conversation with only my ID and ID of the user i'm texting to with the same ConversationId.
Here are my models:
User
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define(
"User",
{
name: { type: DataTypes.STRING },
password: { type: DataTypes.STRING, allowNull: false },
username: { type: DataTypes.STRING, allowNull: false },
email: { type: DataTypes.STRING, allowNull: false },
},
{}
);
User.belongsToMany(models.Conversation, {
as: "conversations",
foreignKey: "user_id",
through: models.ConversationUsers,
});
User.hasMany(models.Message, {
as: "messages",
});
};
return User;
};
Conversation
module.exports = (sequelize, DataTypes) => {
const Conversation = sequelize.define(
"Conversation",
{
lastMessage: DataTypes.STRING,
recipients: DataTypes.ARRAY(DataTypes.INTEGER),
},
{
sequelize,
modelName: "Conversation",
}
);
Conversation.associate = (models) => {
Conversation.belongsToMany(models.User, {
as: "participants",
foreignKey: "conversation_id",
through: models.ConversationUsers,
});
Conversation.hasMany(models.Message, {
as: "messages",
});
};
return Conversation;
};
ConversationUsers Many to Many through model
"use strict";
module.exports = (sequelize, DataTypes) => {
const ConversationUsers = sequelize.define(
"ConversationUsers",
{
user_id: DataTypes.INTEGER,
conversation_id: DataTypes.INTEGER,
},
{
sequelize,
modelName: "ConversationUsers",
}
);
return ConversationUsers;
};
Message
module.exports = (sequelize, DataTypes) => {
const Message = sequelize.define(
"Message",
{
conversationId: { type: DataTypes.INTEGER, allowNull: false },
sentTo: DataTypes.INTEGER,
sentFrom: DataTypes.INTEGER,
body: { type: DataTypes.STRING, allowNull: false },
},
{
sequelize,
modelName: "Message",
}
);
Message.associate = (models) => {
Message.belongsTo(models.User, {
as: "messageTo",
foreignKey: "sentTo",
});
Message.belongsTo(models.User, {
as: "messageFrom",
foreignKey: "sentFrom",
});
Message.belongsTo(models.Conversation, {
as: "messages",
});
};
return Message;
};

I think you can remove some pieces from your models and rework it a bit.
Messages don't need a sentTo, they only need a sentFrom. You can use the ConversationUsers table to know who the recipients are. This also gives you the flexibility to have Conversations with more than 2 members, because your current model essentially enforces that a Message can only be to one user.
So let's walk through the models first with changes
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define(
"User",
{
name: { type: DataTypes.STRING },
password: { type: DataTypes.STRING, allowNull: false },
username: { type: DataTypes.STRING, allowNull: false },
email: { type: DataTypes.STRING, allowNull: false },
},
{
// I think moving the associations to other files might make this more clear
}
);
};
return User;
};
module.exports = (sequelize, DataTypes) => {
const Conversation = sequelize.define(
"Conversation",
{
// perhaps something like a subject could go here e.g.
subject: DataTypes.STRING(500),
},
{
sequelize,
modelName: "Conversation",
}
);
Conversation.associate = (models) => {
Conversation.hasMany(models.Message, {
as: "ConversationMessages",
}); // adds ConversationId onto Message, gives us Conversation.getConversationMessages() etc
models.Message.belongsTo(Conversation); // create association both ways for convenience methods to find convo from a message
models.Message.hasOne(Conversation, {
as: 'LastMessage',
constraints: false,
allowNull:true,
defaultValue:null
}); // adds LastMessageId onto Conversation model (you'll have to write code to maintain this value, probably through an afterCreate hook on Message model)
};
return Conversation;
};
module.exports = (sequelize, DataTypes) => {
const Message = sequelize.define(
"Message",
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true, // if you want to do the hook thing i talked about to set LastMessageId, you need to put this in
},
body: { type: DataTypes.STRING, allowNull: false },
},
{
sequelize,
modelName: "Message",
}
);
Message.associate = (models) => {
Message.belongsTo(models.User, {as: "sentFromUser"});
};
return Message;
};
// I'm going to rename your many-to-many table "ConversationMembers"
module.exports = (sequelize, DataTypes) => {
const ConversationMembers = sequelize.define(
"ConversationMembers",
{
// again, the associations will build these fields for you
},
{
sequelize,
modelName: "ConversationMembers",
}
);
models.Conversation.belongsToMany(models.User, {
through: "ConversationMember",
as: "Members",
}); // gives us Conversation.getMembers()
models.User.belongsToMany(models.Conversation, {
through: "ConversationMember",
as: "MemberConversations",
}); // gives us User.getMemberConversations()
ConversationMember.belongsTo(models.Message, { as: "LastReadMessage" }); // gives us the potential ability to track the last read message for each convo member as ConversationMember.LastReadMessageId, you'll need to set this value manually on read for each user if you care about having it
models.Conversation.hasMany(ConversationMember);
models.User.hasMany(ConversationMember);
return ConversationMember;
Okay now onto your question, which perhaps becomes simpler at this point. If you already know the ConversationId, all you need to do is check that the person who is sending the message is a member of the conversation. Then if they are, write a row into the Messages table. It doesn't matter who the message is "to"--you're writing to the members of the Conversation, not to any individual.
async function canMessageHelper({conversationId, userId }) {
const convo = await models.Conversation.findOne({
attributes: ["id"], // whatever attrs you need, probably not many if any
where: {
id: conversationId,
},
include: [{
model: models.ConversationMember,
attributes: ["ConversationId"], // whatever you need if anything
where: { // this where is critical, it creates an inner join so convo only returns if myUserId is a member of the Conversation
UserId: userId
}
}]
});
if (!convo) {
return false;
}
return convo;
}
async function sendMessage({conversationId, authorUserId, messageText}) {
const allowMessage = await canMessageHelper({conversationId, userId: authorUserId});
if (!allowMessage) {
return false;
}
await models.Message.create({sentFromUserId: authorUserId, body: messageText});
}
If you want to try this, be sure you remove any tables you've already created with these names from your database before you sync.
I have not provided any code for the hooks I mentioned, but you will have the groundwork to develop those ideas out.

Related

Many to many association not returning associated table

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']}})
^^^^^^^^^^^^^^^^^^^^^^

Add default role to many-many relation in Sequelize.js

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

Sequelize associate not created

A very straightforward question. Am using nodejs 5.6, express 4 and sequelize 4. In the program, I have a user object which belongs to company object.
var User = sequelize.define('user', {
username: {
type: Sequelize.STRING
},
password: {
type: Sequelize.STRING
}
});
const Company= sequelize.define('companny', {
record: {
type: Sequelize.JSON
}
});
Company.associate = function(models) {
console.log(models)
Company.hasMany(models.User, {
foreignKey: {
// name: 'user',
allowNull: true
}
})
}
when running the program, the database is created with user and company table, but there is no foreign key in eight user or company. And the console outputs nothing. Any suggestion?
Finally, the index.js file here answers my question.
I would normally use this method to create my models:
// User model
export default (sequelize, DataTypes) => {
const User = sequelize.define('User', {
email: {
allowNull: false,
type: DataTypes.STRING,
unique: true
},
password: {
allowNull: false,
type: DataTypes.STRING,
}
});
return User;
};
// Company model
export default (sequelize, DataTypes) => {
const Company = sequelize.define('Company', {
record: {
type: DataTypes.JSON
}
});
Company.associate = (models) => {
console.log(models)
Company.hasMany(models.User, {
foreignKey: 'userId'
})
}
return Company;
};

How sequelize works?

I'm trying to understand how sequelize works on a simple example : User can have many posts and post can have only one user.
First question, I don't know if I have to use the migrations or the models with sync for creating my database. I mean, I have to put bearly the same code in both. This is the migration for the users table:
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
username: {
allowNull: false,
type: Sequelize.STRING,
unique: true
},
password: {
allowNull: false,
type: Sequelize.STRING
},
email: {
allowNull: false,
type: Sequelize.STRING,
unique: true
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Users');
}
};
And this is the Post model :
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
username: DataTypes.STRING,
password: DataTypes.STRING,
email: DataTypes.STRING
}, {
classMethods: {
associate: (models) => {
User.hasMany(models.Post);
}
}
});
return User;
};
Do I also have to specify that the username, email can't be null and must be unique here in the model?
And how do I have to add the foreign key ? In one tutorial, they said me that the database add automaticly the foreign key but I don't think it works if I use the migrations, I have to set it manualy no?
For your version "sequelize": "^4.13.2":
classMethods and instanceMethods are removed.
Previous:
const Model = sequelize.define('Model', {
...
}, {
classMethods: {
associate: function (model) {...}
},
instanceMethods: {
someMethod: function () { ...}
}
});
New:
const Model = sequelize.define('Model', {
...
});
// Class Method
Model.associate = function (models) {
...associate the models
};
// Instance Method
Model.prototype.someMethod = function () {..}
See official docs Upgrade to V4
So for relations u should walkthrough this steps:
Import models
Call class "associate" method if exists
Export
Example:
// models/index.js
import fs from 'fs';
import path from 'path';
import Sequelize from 'sequelize';
import config from './config';
const sequelize = new Sequelize(config.db.url, config.db.options);
const DB = {};
// Import models
fs
.readdirSync(__dirname)
.filter(file => (file.indexOf('.') !== 0) && (file !== path.basename(__filename)) && (file.slice(-3) === '.js'))
.forEach((file) => {
const model = sequelize.import(path.join(__dirname, file));
DB[model.name] = model;
});
// Here u should call class method for associations
Object.keys(DB).forEach((modelName) => {
if ('associate' in DB[modelName]) {
DB[modelName].associate(DB);
}
});
DB.sequelize = sequelize;
DB.Sequelize = Sequelize;
export default DB;
All relations u can put in your models.
User:
// models/user.js
export default (sequelize, DataTypes) => {
const User = sequelize.define(
'users',
// Fields
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
// etc ...
},
// Options
{
timestamps: false, // <-- turn off timestamps
underscored: true, // <-- this option for naming with underscore. example: createdAt -> created_at
validate: {},
indexes: [],
},
);
User.associate = (models) => {
User.hasMany(models.post, {
// ...
});
User.hasMany(models.comment, {
// ...
});
// OR
models.user.hasMany(models.post, {
// ...
});
};
return User;
};
Post:
// models/post.js
export default (sequelize, DataTypes) => {
const Post = sequelize.define(
'posts',
// Fields
{
// ...
},
// Options
{
// ...
},
);
Post.associate = (models) => {
Post.belongsTo(models.user, {
// ...
});
// OR
models.post.belongsTo(models.user, {
// ...
});
};
return Post;
};
Do I also have to specify that the username, email can't be null and
must be unique here in the model?
Yes u should define all things in your models, such as keys, relations, whatever. Because your app use models for actions with database.
And how do I have to add the foreign key ? In one tutorial, they said
me that the database add automaticly the foreign key but I don't think
it works if I use the migrations, I have to set it manualy no?
Actually u cant define composite keys in migrations that creates the table and fields.
Best practise for migrations should be like this:
000000_create_users_table
000001_add_foreign_keys_to_users_table
000002_add_new_field_to_users_table
etc...
So u should add all things manually in migrations.
For adding indexes in migrations you should use queryInterface.addIndex
module.exports = {
up: queryInterface => queryInterface.addIndex(
'users',
{
unique: true,
fields: ['username', 'email'],
// if u want to rename u can use:
// name: 'whatever'
// by convention default name will be: table_field1_fieldN
},
),
down: queryInterface => queryInterface.removeIndex(
'users',
'users_username_email', // <-- this name by convention, but u can rename it
),
};
For "keys" you should use queryInterface.addConstraint
Primary Key
queryInterface.addConstraint('Users', ['username'], {
type: 'primary key',
name: 'custom_primary_constraint_name'
});
Foreign Key
queryInterface.addConstraint('Posts', ['username'], {
type: 'FOREIGN KEY',
name: 'custom_fkey_constraint_name',
references: { //Required field
table: 'target_table_name',
field: 'target_column_name'
},
onDelete: 'cascade',
onUpdate: 'cascade'
});
Check all API References
You are right you have to manually set the foreign key relations.
Here is official documentation link : http://docs.sequelizejs.com/manual/tutorial/associations.html
You can try following code:
var user_object = require('your_file_path');
var post_object = require('your_file_path');
user_object.hasMany(post_object, {
foreignKey: 'user_id',
sourceKey: 'user_id',
onDelete: 'cascade',
as:'Posts',
});
post_object.belongsTo(user_object, {
foreignKey: 'user_id',
sourceKey: 'user_id',
onDelete: 'cascade',
as:'Posts',
});
I am really just restrucuring your code.
// Create One database config file
var Sequelize=require('sequelize');
var connection=new Sequelize('project','user','password',{
dialect:'mysql',
logging:false
});
connection.authenticate()
.then(() => {
console.log("Connected to database");
})
.catch(err => {
//console.error("Can't connect to database :(\n", err);
});
module.exports={
database:connection,
}
//Your User Schema File
var database = require('your_file_path/DatabaseConnection').database;
var Sequelize = require('sequelize');
var Users = database.define('users', {
username: {
allowNull: false,
type: Sequelize.STRING,
unique: true
},
password: {
allowNull: false,
type: Sequelize.STRING
},
email: {
allowNull: false,
type: Sequelize.STRING,
unique: true
}
}, {
underscored: true
},hooks: {
beforeCreate: (user, option) => {
users.password = encrypto.encryptEntity(user.password);
//for automatic encryption of password
},
}
);
Users.sync();
//id, updated_at , and modified_at will be maintained by default
module.exports = {
Users
}
// your post file path
var Posts = database.define('posts', {
post_content: {
allowNull: false,
type: Sequelize.STRING,
unique: true
}
}, {
underscored: true
});
//importing User
var Users = require('file_path')
Users.hasMany(Posts, {
foreignKey: 'user_id',
sourceKey: 'user_id',
onDelete: 'cascade',
as:'Posts',
});
Posts.belongsTo(Users, {
foreignKey: 'user_id',
sourceKey: 'user_id',
onDelete: 'cascade',
as:'Users',
});
// two way binding.
Posts.sync();
BY maintaining Relation you can easily update data using setter and getter methods
Posts.setUsers(user_object);
// above code will automatically put the user_id found in user_object
//for select query you can use:
Users.findOne({
where:{
id:user_id
},
include: [{
model: Posts,
attributes: ['post_content'],
as: "Posts"
}//this will bring every posts user has created
})
I think above coding standard will make your code looks cleaner and will be more helpful for larger projects.
Hope this helps.

hasMany called with something that's not an instance of Sequelize.Model

as you guys can see my issue is related to the title description, i created a User Model, and a Foto Model in sequelize, basicly a user can shoot many fotos, but each foto can be related to just 1 user.
My User model
"use strict";
var sequelize = require('./index');
var bcrypt = require('bcrypt-nodejs');
var Foto = require('./Foto');
module.exports = function (sequelize, DataTypes) {
var User = sequelize.define("User", {
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isUnique: function (value, next) {
var self = this;
User.find({ where: { username: value } })
.then(function (user) {
// reject if a different user wants to use the same username
if (user && self.id !== user.id) {
return next('username already in use!');
}
return next();
})
.catch(function (err) {
return next(err);
});
}
}
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isUnique: function (value, next) {
var self = this;
User.find({ where: { email: value } })
.then(function (user) {
// reject if a different user wants to use the same email
if (user && self.id !== user.id) {
return next('Email already in use!');
}
return next();
})
.catch(function (err) {
return next(err);
});
}
}
},
typeOfUser: {
type: DataTypes.INTEGER,
allowNull:true,
defaultValue:null
},
country: {
type: DataTypes.STRING,
allowNull:true,
defaultValue:null
},
birthDate:{
type: DataTypes.DATEONLY,
allowNull:true,
defaultValue:null
},
reports: {
type: DataTypes.INTEGER,
defaultValue: 0
},
points: {
type: DataTypes.INTEGER,
defaultValue: 0
},
password: {
type: DataTypes.STRING,
allowNull:false
},
numberFotos: {
type: DataTypes.INTEGER,
defaultValue: 0
}
}, {
classMethods: {
generateHash: function (password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
},
},
instanceMethods: {
validPassword: function (password) {
return bcrypt.compareSync(password, this.password);
}
}
});
User.hasMany(Foto,{as: 'fotos', foreignKey: 'userId'})
return Foto;
}
My foto model
"use strict";
var sequelize = require('./index');
var bcrypt = require('bcrypt-nodejs');
var User = require('./User');
module.exports = function (sequelize, DataTypes) {
var Foto = sequelize.define("Foto", {
reports: {
type: DataTypes.INTEGER,
defaultValue: 0
},
image: {
type: DataTypes.STRING,
allowNull: false
},
date: {
type: DataTypes.DATE,
allowNull:true
},
position: {
type: DataTypes.RANGE,
allowNull: true
}
});
Foto.belongsTo(User, {foreignKey: 'userId'});
return Foto;
}
You don't need to declare the association on the Photo Model:
Foto.belongsTo(User, {foreignKey: 'userId'});
When you have a 1:N relation between models you only need to refer the id from the "1" model, on our case the User model, on the "N" model, Photos. So doing:
User.hasMany(Foto,{as: 'fotos', foreignKey: 'userId'})
Will create a column on your Foto table with name "userId" that refer to user table. On this way both models are associate as you want.
You can define relations for both models in one file. It doesn't throw any errors that way.
In your Foto.js, you can try:
...
Foto.belongsTo(User);
User.hasMany(Foto);
return Foto;
I had a similar problem. Sometimes it can be caused because in your index.js or app.js the files are loaded in a specific order, so for example if you have a relationship between A and B, A loads first and references B, and B in turn references A, the error will be thrown inside the B file because A has not been fully defined/executed yet.
The solution to this would be to remove all associations from the model files, and inside your app or index.js require them all, and then define their relationships.
Example
const entities = {
A: require('./src/Entity/A'),
B: require('./src/Entity/B'),
};
entities.A.belongsToMany(entities.B, {through: 'AB'});
entities.B.belongsToMany(entities.A, {through: 'AB'});
So I was getting this error and it took me some time to deal with the bug. I realised I was getting the Error because I was referencing the model wrongly. Sequelize is case sensitive so if you created the model with UpperCase ensure to keep it uniform throughout your referencing.
I would also point out you could try this out instead
User.hasMany(models.Foto ,{as: 'fotos', foreignKey: 'userId'})
It seems you need to define both ends of the relationship in the file containing the 1 part of the 1:many association. That is, the "User" file in your case.
So:
User.hasMany(Foto);
Foto.belongsTo(User);
None of the above solutions worked for my scenario (could work for other setups). I stumbled upon this article which states you have to have the models defined and exported prior to applying the associations. Using a separate extra-setup.js file to define the associations, worked for me.
https://github.com/sequelize/express-example/tree/master/express-main-example
I had lots of issues, but I switched to using the sequelize CLI which generated models in this format, I then found creating associations a lot easier as the index file took care of everything and the static associate({ PersonalDetail }) that is in the model itself already requires your models in one place all you need to do is deconstruct them, so no need to require anything at the top of the file.
This youtube video really helped me out... https://www.youtube.com/watch?v=3qlnR9hK-lQ
'use strict'
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({ PersonalDetail }) {
// define association here
this.hasMany(PersonalDetail, {
foreignKey: 'userId',
//as: 'personalDetails',
})
}
}
User.init(
{
uuid: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
moredata below: {
type: DataTypes.STRING,
allowNull: false,
},
//createdAt/updatedAt is defined in migration and updated automatically
},
{
sequelize,
tableName: 'users',
modelName: 'User',
}
)
return User
}
I got the same type issue. All mappings were done perfectly as explained in the document.
Yet, I received the issue regarding the association.
Reason is given by Dorian in this forum.
https://stackoverflow.com/a/60760296/16790144
My approach:
models/company.js
const company = sequelize.define("company",{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
companyName: {
type: DataTypes.STRING,
allowNull: false,
}
});
export default company;
models/client.js
const Client = sequelize.define("client", {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
firstName: {
type: DataTypes.STRING,
allowNull: false,
}
});
export default Client;
models/clientCompany.js
const clientCompany = sequelize.define("client_company",{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
companyId: {
type: DataTypes.INTEGER
},
clientId: {
type: DataTypes.INTEGER
}
});
export default clientCompany;
models/index.js
import Company from './company';
import Client from './client';
import ClientCompany from './clientCompany';
Company.belongsToMany(Client, { through : ClientCompany });
Client.belongsToMany(Company, { through : ClientCompany });
export {
Company,
Client,
ClientCompany,
};
handler.js
This file contains the business logic.
import { Client, Company } from '../../models';
const company = await Company.findOne({
where: { id: companyId },
include: Client,
});

Resources