Sequelize cyclic dependency - node.js

This is my User model
'use strict';
var bcrypt = require('bcrypt');
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('User', {
email: {
type: DataTypes.STRING,
validate: {
isEmail: true,
notEmpty: true,
notNull: false
},
unique: true
},
password: DataTypes.STRING,
name: DataTypes.STRING,
username: {
type: DataTypes.STRING,
unique: true
},
admin: DataTypes.BOOLEAN,
googleId: DataTypes.BOOLEAN
}, {
classMethods: {
associate: function(models) {
User.hasMany(models.Award);
User.hasMany(models.Media);
User.hasMany(models.Comment);
User.hasMany(models.Like);
User.hasMany(models.CheckIn);
}
}
});
return User;
};
and this is my Media model:
'use strict';
module.exports = function(sequelize, DataTypes) {
var Media = sequelize.define('Media', {
type: DataTypes.ENUM('photo', 'video'),
description: DataTypes.STRING,
url: DataTypes.STRING,
gps: DataTypes.GEOMETRY('POINT')
}, {
classMethods: {
associate: function(models) {
//Media.belongsTo(models.Event);
//Media.belongsTo(models.User);
Media.hasMany(models.Comment);
Media.hasMany(models.Like);
}
}
});
return Media;
};
And I'm getting this error:
Unhandled rejection Error: Cyclic dependency found. Users is dependent of itself.
Dependency chain: Awards -> Users -> Media => Users
Previously I had a cyclic dependency and it's now removed but sequelize still throws this error. Why is this happening?
If I remove the User.hasMany(models.Media) association the error will disappear. But why is it still happening when the Media model has no reference to the User model?

Consider using hasMany as follows:
User.hasMany(models.Award, {as: 'ifYouWantAlias', constraints: false, allowNull:true, defaultValue:null});
note that you don't need the following part but it makes it clearer in my opinion.
allowNull:true, defaultValue:null
It is explained well in here: http://docs.sequelizejs.com/en/latest/api/associations/

Settings constraints: false will work, but will not create the foreign key in your DB.
If all your db access is done from sequelize, that can be an acceptable solution. But if you access your DB in several ways, that becomes complicated to handle. E.g: Hasura does not create relationships between models.
The real issue is that sequelize is not smart enough to create the table in 2 steps.
Other ORMs handle that by first creating the tableA without the foreign key, create the tableB with foreignKey to tableA, and alter TableA to create foreign key to tableB.
So the solution is to add constraints: false to your User.hasMany(models.Media) then create then run foreign key constrain.
const addMediaUserForeignKey = queryInterface.addConstraint(
'Media', {
type: 'foreign key',
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
references: {
table: 'User',
field: 'id',
},
fields: [ 'userId' ]
}
).catch((e) => {})

Related

How to fix? Unhandled rejection SequelizeForeignKeyConstraintError: insert or update on table "x" violates foreign key constraint "y_fkey"

This error is showing when I attempt to POST / create a new contact in the database. This is the error Unhandled rejection SequelizeForeignKeyConstraintError: insert or update on table "contacts" violates foreign key constraint "org_name_fkey". Below are the models, does anyone know how to fix this? All of the other fields POST fine, it is only when attempted to add org_name. If I don't include org_name in the POST everything stores in the postgres database.
Contact Model
module.exports = (sequelize, DataTypes) => {
const Contacts = sequelize.define('contact', {
contact_id: {
type: Sequelize.INTEGER,
field: 'contact_id',
primaryKey: 'true'
},
first_name: {
type: Sequelize.STRING,
field: 'first_name'
},
last_name: {
type: Sequelize.STRING,
field: 'last_name'
},
contact_type_id: {
type: Sequelize.STRING,
references: {
model: 'contact_type',
key: 'contact_type_id'
}
},
org_name: {
type: Sequelize.STRING,
references: {
model: 'org',
key: 'org_name'
}
}
},
{
tableName: 'contacts',
}
);
Contacts.associate = (models) => {
Contacts.belongsTo(models.contact_type, {foreignKey: 'contact_type_id'});
Contacts.belongsTo(models.org, {foreignKey: 'org_name'});
};
return Contacts;
};
Contact Type Model
module.exports = (sequelize, DataTypes) => {
const ContactType = sequelize.define('contact_type', {
// attributes
contact_type_id: {
type: Sequelize.STRING,
field: 'contact_type_id'
},
updated_at: {
type: Sequelize.DATE,
field: 'updated_at'
},
created_at: {
type: Sequelize.DATE,
field: 'created_at'
}
},
);
ContactType.associate = (models) => {
ContactType.hasOne(models.contact, {foreignKey: 'contact_id'});
};
return ContactType;
};
Org Model
module.exports = (sequelize, DataTypes) => {
const Org = sequelize.define('org', {
org_name: {
type: Sequelize.STRING,
field: 'org_name',
primaryKey: 'true'
},
org_type_id: {
type: Sequelize.STRING,
field: 'org_type_id'
},
website: {
type: Sequelize.STRING,
field: 'website'
}
},
);
Org.associate = (models) => {
Org.hasMany(models.contact, {foreignKey: 'contact_id'});
};
return Org;
};
What that error tells you is that whatever org_name you are passing into your POST request does not have a corresponding Org record in the database. i.e. You are creating a person who works at ABC Corp, but you haven't added ABC Corp to your Orgs. You would have to fix this by creating the Org first, then the Contact.
You do not give the exact request that causes the error. This would be helpful in tracking down your issue. Also, you should query your database to find the state of the Org table before the POST. Perhaps you forgot to call save on the Org instance.

Foreign field reference in Sequelize model files

I am writing a lead capture app using nodejs, express and sequelize. I have a Lead object, but I want to introduce a new table LeadSource to the schema, with Lead having a FK to LeadSource
/path/to/model/Lead.js
'use strict';
module.exports = (sequelize, DataTypes) => {
var Lead = sequelize.define('Lead', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
primaryKey: true
},
first_name: {
type: DataTypes.STRING,
},
last_name: {
type: DataTypes.STRING,
},
email: {
type: DataTypes.STRING,
allowNull: false,
},
});
return Lead;
};
This is the schema for LeadSource
LeadSource = sequelize.define('LeadSource', {
id: { type: Sequelize.INTEGER },
name: { type: Sequelize.STRING }
});
My questions are:
How do I reference LeadSource as FK in the Lead.js file?
I like to keep each model in a seperate file - can I have this separation and still have LeadSource reference by a FK in Lead?
Yes, you can define the relationships between the models while keeping each one in a separate file.
Here is how you can define associations between Lead and LeadSource in Lead.js file
module.exports = (sequelize, DataTypes) => {
var Lead = sequelize.define('Lead', {
// ...
})
Lead.associate = function (models) {
// associations can be defined here
//One-To-One relationship with foreign key added to Lead
Lead.belongsTo(models.LeadSource, {
foreignKey: { name: 'leadSourceId' }
})
//One-To-Many relationship with foreign key added to Lead
models.LeadSource.hasMany(Lead, {
foreignKey: { name: 'leadSourceId' }
})
}
return Lead;
}

Why doesn't sequelize-cli include the ID in the model file?

When I run the following command:
sequelize-cli model:create --name User --attributes "dispName:string,email:string,phoneNum1:string"
I end up with the following migration file:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
dispName: {
type: Sequelize.STRING
},
email: {
type: Sequelize.STRING
},
phoneNum1: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Users');
}
};
and the following model file:
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
dispName: DataTypes.STRING,
email: DataTypes.STRING,
phoneNum1: DataTypes.STRING
}, {});
User.associate = function(models) {
// associations can be defined here
};
return User;
};
Questions:
Why doesn't the model file contain the definition for id?
If I change the name and/or definition of the primary key field (the key will be generated externally and will be set on an object before it is saved), then do I have to include the definition of the ID field in the model file? Why/why not?
Versions:
[Node: 12.14.1, CLI: 5.5.1, ORM: 5.21.3]
The following do not answer my question:
Should Sequelize migrations update model files?
If you don't declare PK in your model sequelize assumes you have the id PK. This is by design. And yes you can rename your PK in the model. Just don't forget to setup PK in the mode properly according to a real PK in your DB.

Problem setting up Sequelize association - query with 'include' is failing

I'm new to Sequelize and trying to test if an n:m association I set up between two models, User and Podcast, is working. When I try to run this query, I get some kind of DB error that isn't specific about what's wrong:
User.findOne({
where: { id: id },
include: [{ model: Podcast }]
});
Does anyone know what I'm messing up? I suspect there's something wrong in how I've set up the association, like I'm referencing the names of tables slightly incorrectly, but the migration to create the association worked.
Here's my User.js model file:
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
photo: {
type: DataTypes.STRING
}
});
User.associate = function(models) {
// associations can be defined here
User.belongsToMany(models.Podcast, {
through: 'user_podcast'
});
};
return User;
};
And here's my Podcast.js file:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Podcast = sequelize.define('Podcast', {
id: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false
},
title: {
type: DataTypes.STRING,
allowNull: false
},
thumbnail: {
type: DataTypes.STRING
},
website: {
type: DataTypes.STRING
}
});
Podcast.associate = function(models) {
// associations can be defined here
Podcast.belongsToMany(models.User, {
through: 'user_podcast'
});
};
return Podcast;
};
And here's the migration I ran to join the two tables:
'use strict';
module.exports = {
up: function(queryInterface, Sequelize) {
return queryInterface.createTable('user_podcast', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
userId: {
type: Sequelize.INTEGER,
references: {
model: 'Users',
key: 'id'
}
},
podcastId: {
type: Sequelize.STRING,
references: {
model: 'Podcasts',
key: 'id'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: function(queryInterface, Sequelize) {
return queryInterface.dropTable('user_podcast');
}
};
And here's the project on Github for further reference:
https://github.com/olliebeannn/chatterpod
You don't need to create a migration for the M:N table. Now you have something wrong on your user_podcast model. If you are setting a M:N relation between to tables your primary key will be the combination between the foreign key from these two models. If you still want a single id primary key for your table, then you won't use belongsToMany instead use hasMany on user and podcast models pointing to a new model user_podcast.
As far as I see on your first query, it seems that you really need a M:N relation so you can define the model as you do with user and podcast like this:
module.exports = (sequelize, DataTypes) => {
const UserPodcast = sequelize.define('user_podcast', {
userId: {
// field: 'user_id', #Use 'field' attribute is you have to match a different format name on the db
type: DataTypes.INTEGER
},
podcastId: {
// field: 'podcast_id',
type: DataTypes.INTEGER
},
});
UserPodcast.associate = function(models) {
models.User.belongsToMany(models.Podcast, {
as: 'podcasts', //this is very important
through: { model: UserPodcast },
// foreignKey: 'user_id'
});
models.Podcast.belongsToMany(models.User, {
as: 'users',
through: { model: UserPodcast },
// foreignKey: 'podcast_id'
});
};
return UserPodcast;
};
I do prefer to have the belongsToMany associations on the save function where I define the join model, and you have to notice that I used as: attribute on the association. This is very important because this will help sequelize to know which association are you referring on the query.
User.findOne({
where: { id: id },
include: [{
model: Podcast,
as: 'podcasts' //here I use the previous alias
}]
});

Node.js Sequelize UUID primary key + Postgres

I try to create database model by using sequelize but I'm facing a problem with model's primary key.
Setting
I'm using Postgres (v10) in docker container and sequalize (Node.js v10.1.0
) for models and GraphQL (0.13.2) + GraphQL-Sequalize (8.1.0) for request processing.
Problem
After creating models by sequelize-cli I've manually tried to replace id column with uuid. Here's my model migration that I'm using.
'use strict';
const DataTypes = require('sequelize').DataTypes;
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Currencies', {
uuid: {
primaryKey: true,
type: Sequelize.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false
},
name: {
type: Sequelize.STRING
},
ticker: {
type: Sequelize.STRING
},
alt_tickers: {
type: Sequelize.ARRAY(Sequelize.STRING)
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Currencies');
}
};
Model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Currency = sequelize.define('Currency', {
uuid: DataTypes.UUID,
name: DataTypes.STRING,
ticker: DataTypes.STRING,
alt_tickers: DataTypes.ARRAY(DataTypes.STRING)
}, {});
Currency.associate = function(models) {
// associations can be defined here
};
return Currency;
};
Due to some problem sequalize executes next expression:
Executing (default): SELECT "id", "uuid", "name", "ticker", "alt_tickers", "createdAt", "updatedAt" FROM "Currencies" AS "Currency" ORDER BY "Currency"."id" ASC;
That leads to "column 'id' doesn't exist" error.
Alternatively, I've tried to fix it by renaming uuid column to id at migration:
...
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4()
},
...
And at the model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Currency = sequelize.define('Currency', {
id: DataTypes.INTEGER,
name: DataTypes.STRING,
ticker: DataTypes.STRING,
alt_tickers: DataTypes.ARRAY(DataTypes.STRING)
}, {});
Currency.associate = function(models) {
// associations can be defined here
};
return Currency;
};
but the result was the following error at the start of the program:
Error: A column called 'id' was added to the attributes of 'Currencies' but not marked with 'primaryKey: true'
Questions
So, is there a way to force sequelize to use UUID as the tables primary key without defining id column?
Is there a way to create columns without id columns?
What possibly caused this errors and how should fix it?
Thanks in advance!
What you have not posted here is your model code. This is what I think has happened
The database has been manually changed from id to uuid.
Your model does not reflect this change.
Hence the query is searching for both id and uuid.
You can fix this my defining uuid in your model like below and making it a primary key
const User = sequelize.define('user', {
uuid: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV1,
primaryKey: true
},
username: Sequelize.STRING,
});
sequelize.sync({ force: true })
.then(() => User.create({
username: 'test123'
}).then((user) => {
console.log(user);
}));
This is just about the only resource I've found online that explains what it takes to set up a UUID column that the database provides defaults for, without relying on the third-party uuid npm package: https://krmannix.com/2017/05/23/postgres-autogenerated-uuids-with-sequelize/
Short version:
You'll need to install the "uuid-ossp" postgres extension, using a sqlz migration
When defining the table, use this defaultValue: Sequelize.literal( 'uuid_generate_v4()' )

Resources