Sequelize associate not creating foreign key - mysql

According to sequelize documentation, Model.hasMany should automatically create foreign key references with constraints. But when i try it in my code it does not. I have to manually add the foreign key to the migration. Please how can i fix this? This is my User model
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
name: DataTypes.STRING,
username: DataTypes.STRING,
email: DataTypes.STRING,
password: DataTypes.STRING
});
User.associate = function(models) {
// associations can be defined here
User.hasMany(models.Post);
};
return User;
};
and this is my post model
'use strict';
module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define('Post', {
title: DataTypes.STRING,
body: DataTypes.TEXT
});
Post.associate = function(models) {
Post.belongsTo(models.User);
};
return Post;
};

To create foreign key, you'll need to put the information of which column is the primary key and the foreign key referred:
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
name: {
DataTypes.STRING,
primaryKey: true
},
username: DataTypes.STRING,
email: DataTypes.STRING,
password: DataTypes.STRING
});
User.associate = function(models) {
// associations can be defined here
User.hasMany(models.Post, {
foreignKey: modelId
);
};
return User;
};
Note that, in your User, I defined which column is the primary key and, in hasMany, I defined which column on Post is the foreignKey.

After much struggle with this I finally figured it out.
The db:migrate command uses the migration files and does not include the model files.
To use the model files instead, I just needed to do this in the app.js file
...
...
const db = require('./models');
db.sequelize.init();
Although a lot of experience programmers do not recommend this method. They suggest that the best method is manually creating the foreign key in the migration files.

Related

Sequelize error "Unknown column in 'field list'" during CREATE for One-to-Many Association

I have been using the following tutorial to learn how to implement one-to-many relationship in Sequelize where a Tutorial has many Comment and Comment belongs to a Tutorial: https://www.bezkoder.com/sequelize-associate-one-to-many/
While I have the code modeling what is documented for setting up the relationship between the two models, I receive the following error during creating the Comment model:
Unknown column 'tutorialId' in 'field list'
Additionally, I receive the following SQL output:
Executing (default): INSERT INTO `comments` (`id`,`name`,`text`,`createdAt`,`updatedAt`,`tutorialId`) VALUES (DEFAULT,?,?,?,?,?);
app.js
const tutorialsRouter = require('./routes/api/tutorials');
const commentsRouter = require('./routes/api/comments');
app.use('/api/tutorials', tutorialsRouter);
tutorialsRouter.use('/:tutorialId/comments', commentsRouter);
/models/index.js
db.tutorials = require("./tutorial.model")(sequelize, Sequelize);
db.comments = require("./comment.model")(sequelize, Sequelize);
db.tutorials.hasMany(db.comments, { as: "comments" });
db.comments.belongsTo(db.tutorials, {
foreignKey: "tutorialId",
as: "tutorial",
});
/models/comment.model.js
module.exports = (sequelize, DataTypes) => {
const Comment = sequelize.define('comment', {
name: {
type: DataTypes.STRING,
},
text: {
type: DataTypes.STRING,
},
});
return Comment;
}
/routes/comments.js
const comments = require('../../controllers/comments.controller');
const router = require('express').Router({ mergeParams: true });
router.post('/', comments.create);
module.exports = router;
/controllers/comments.controller.js
const db = require('../models');
const Comment = db.comments;
exports.create = (req, res) => {
...
Comment.create({
name: req.body.name,
text: req.body.text,
tutorialId: req.params.tutorialId,
})
.then( ... )
.catch( ... );
}
Then in Postman I receive a 500 (of the error message above) when issuing the request:
POST localhost:3000/api/tutorials/1/comments
{
"name": "John Doe",
"text": "Lorem ipsum..."
}
I don't think I should have to define a tutorialId field on the Comment model. Grr...
This may be very obvious to some of you, but it's tripping me up trying to learn. Any help is very much appreciated. :)
The issue that you're having is a result of using aliases via the as property. See the docs for belongsTo and hasMany. Here's a code sample that performs the inserts without the error.
let {
Sequelize,
DataTypes,
} = require('sequelize')
async function run () {
let sequelize = new Sequelize('dbname', 'username', 'password', {
host: 'localhost',
port: 5555,
dialect: 'postgres',
logging: console.log
})
let Comment = sequelize.define('comment', {
name: {
type: DataTypes.STRING,
},
text: {
type: DataTypes.STRING,
},
})
let Tutorial = sequelize.define('tutorial', {
title: {
type: DataTypes.STRING,
},
content: {
type: DataTypes.STRING,
}
})
Tutorial.hasMany(Comment)
Comment.belongsTo(Tutorial)
// This just recreates the tables in the database.
// You would really only want to use a force sync
// in a development environment, since it will destroy
// all of the data....
await sequelize.sync({ force: true })
let tutorial = await Tutorial.create({
title: 'Tutorial',
content: 'Hmm....'
})
let comment = await Comment.create({
name: 'Comment',
text: 'Something, something....',
tutorialId: tutorial.id,
})
await sequelize.close()
}
run()
Edit
This is just an edit to my original answer above. The OP Tom Doe discovered that the issue was being caused by a mismatch between the definitions of the tables in the database and the models defined via sequelize (see comments below). As we discovered, one way to troubleshoot the mismatch is to force sync a new version of the database, and then compare the new version of the database with the original version. There may be differences in the definitions of the columns or the contraints. Force syncing the database can be done via the command
await sequelize.sync({ force: true})
Important Note: The above statement will overwrite the existing database and all of its data. See the docs for more information.

Sequelize associations not being created in MySQL, PostgreSQL and SQLite

I am defining associations in models using sequalize with MYSQL. But after migration, the foreign key is not being added to the target model as explained in sequelize docs.
I have also tried to manually define foreign keys in models and migration files but still no association is being created between tables. When I view the tables in relation view in PhpMyAdmin, not foreign key constraints or relationship is being created.
I have tried this with SQLite, and PostgreSQL with the same results. I don't know what I am doing wrong. Here are models.
AURHOR MODEL
//One author hasMany books
'use strict';
module.exports = (sequelize, DataTypes) => {
const Author = sequelize.define('Author', {
Name: DataTypes.STRING
}, {});
Author.associate = function(models) {
// associations can be defined here
Author.hasMany(models.Book)
};
return Author;
};
I expect sequelize to add authorId on books table as specified in the docs, but this not happening
BOOK MODEL
//Book belongs to Author
'use strict';
module.exports = (sequelize, DataTypes) => {
const Book = sequelize.define('Book', {
Title: DataTypes.STRING
}, {});
Book.associate = function(models) {
// associations can be defined here
Book.belongsTo(models.Author)
};
return Book;
};
No associations is being created between these two tables after migration.
I have as well tried to define custom foreign keys in model associations like this:
//Author model
Author.hasMany(models.Book,{foreignKey:'AuthorId'})
//Book model
Book.belongsTo(models.Author,{foreignKey:'AuthorId'})
still this not solving the problem
I have gone ahead to define foreign keys in models then referencing them in the association like this:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Book = sequelize.define('Book', {
Title: DataTypes.STRING,
AuthorId:DataTypes.INTEGER
}, {});
Book.associate = function(models) {
// associations can be defined here
Book.belongsTo(models.Author,{foreignKey:'AuthorId'})
};
return Book;
};
But still no associations is being created
I finally decided to add references in migration files like so:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Books', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
Title: {
type: Sequelize.STRING
},
AuthorId:{
type: Sequelize.INTEGER,
references:{
model:'Author',
key:'id'
}
}
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Books');
}
};
But when I run this kind of migration setup, I get this error: ERROR: Can't create table dbname.books (errno: 150 "Foreign key constraint is i
ncorrectly formed")
I get similar error when I switch to PostgreSQL.
I have been held back by this issue for quite long. What may I doing wrong. I am using sequelize version 4.31.2 with sequelize CLI.
I was referencing to models wrongly in migrations.
Wrong way
AuthorId:{
type: Sequelize.INTEGER,
references:{
model:'Author',
key:'id'
}
}
Correct Way
// Notes the model value is in lower case and plural just like the table name in the database
AuthorId:{
type: Sequelize.INTEGER,
references:{
**model:'authors',**
key:'id'
}
}
This solved my problem. The associations is now getting defined.

How to define an index, within a Sequelize model?

I'm trying to create a simple non-unique index for one of my SQL columns, inside a Sequelize model. I tried to follow this post :How to define unique index on multiple columns in sequelize .
This is my code:
module.exports = (sequelize, DataTypes) => {
const Item = sequelize.define('Item', {
itemId: DataTypes.STRING,
ownerId: DataTypes.INTEGER,
status: DataTypes.STRING,
type: DataTypes.STRING,
nature: DataTypes.STRING,
content: DataTypes.STRING,
moment: DataTypes.BIGINT,
indexes:[
{
unique: 'false',
fields:['ownerId']
}
]
});
return Item;
};
I get this error:
Unhandled rejection SequelizeDatabaseError: You have an error in your
SQL syntax; check the manual that corresponds to your MariaDB server
version for the right syntax to use near '[object Object], createdAt
DATETIME NOT NULL, updatedAt DATETIME NOT NULL, P' at line 1
The code that i have in my server.js file is this:
models.sequelize.sync().then(function () {
server.listen(port, () => {
console.log('server ready')
})
});
What is wrong with my setup? Is there any other way this can be done with Sequelize?
Almost there. You should add indexes in a new object like this:
module.exports = (sequelize, DataTypes) => {
const Item = sequelize.define('Item', {
itemId: DataTypes.STRING,
ownerId: DataTypes.INTEGER,
status: DataTypes.STRING,
type: DataTypes.STRING,
nature: DataTypes.STRING,
content: DataTypes.STRING,
moment: DataTypes.BIGINT
},
{
indexes:[
{
unique: false,
fields:['ownerId']
}
]
});
return Item;
};
It can work in single migration also.
In my case, just perform the addIndex after createTable method in the migration file
Migration:
return queryInterface.createTable('Item', {
// columns...
}).then(() => queryInterface.addIndex('Item', ['OwnerId']))
.then(() => {
// perform further operations if needed
});
it's work for me in the migration file.

Unique email address with Sequelize

I'm running ExpressJS with Sequelize/MySQL and trying very hard to get a simple validator check working for unique email address.
Here is my user model. And for the life of me I don't understand why this is allowing records that have duplicate email address. Surely the email.unique=true would be preventing this.
'use strict';
module.exports = (sequelize, DataTypes) => {
var User = sequelize.define('User', {
firstName: DataTypes.STRING,
lastName: DataTypes.STRING,
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: {
msg: "Must be a valid email address",
}
}
}
}, {
indexes: [{
fields: ['email'],
unique: true,
}]
});
return User;
};
Any help appreciated.
EDIT:
As requested here is the controller code for create user.
const User = require('../models').User;
exports.create = (req, res) => {
User.create( req.body )
.then( user => {
res.json( user );
})
.catch( errors => {
res.json({ errors: errors.errors });
});
};
One way to solve this is by using sequelize.sync() to create your table according to the schema specified in the model if the table exists then you should pass {force: true} to the sync method, the table will be dropped and a new one will be created.
though using sequelize.sync() is not highly recommended especially in production due to issues with migration files etc, you can google than for more details.

Sequelize cli creating association belongsto user or staff

i found many things about associations but nothing to my particular case,
i created some models and i'm trying to associate them,
so i think it is a matter of understanding database modeling too.
I've got the models user and staff, both share an attribute user_id.
user.js
'use strict';
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('User', {
user_id: DataTypes.STRING,
fullname: DataTypes.STRING,
username: DataTypes.STRING,
comment: DataTypes.TEXT
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
User.hasMany(models.Sshkey, {foreignKey: 'sshkey_id'})
}
}
});
return User;
};
staff.js
'use strict';
module.exports = function(sequelize, DataTypes) {
var Staff = sequelize.define('Staff', {
user_id: DataTypes.STRING,
fullname: DataTypes.STRING,
username: DataTypes.STRING,
password: DataTypes.STRING,
isAdmin: DataTypes.BOOLEAN
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
Staff.hasMany(models.Sshkey, {foreignKey: 'sshkey_id'})
}
}
});
return Staff;
};
And i've got a model sshkey which can belong to either an user or a staff member.
I'm using Sequelize cli and haven't done any migrations yet.
And i'm pretty new to Js and creating databases, thinking about the database models and the associations, and i'm curios if i could write or do such thing as:
'use strict';
module.exports = function(sequelize, DataTypes) {
var Sshkey = sequelize.define('Sshkey', {
sshkey_id: DataTypes.STRING,
sshkey: DataTypes.TEXT
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
// My Problem starts here |
// Should i write |
// |
// V
Sshkey.hasOne(models.User || models.Staff, {foreignKey: 'user_id'})
// Or maybe:
// Sshkey.hasOne(models.User, {foreignKey: 'user_id'}) ||
// Sshkey.hasOne(models.Staff, {foreignKey: 'user_id'})
// Should i rather rename models.Staffs foreignKey user_id to staff_id?
// Or maybe:
// Sshkey.hasOne(models.User, {as: 'userkey', foreignKey: 'user_id'})
// Sshkey.hasOne(models.Staff, {as: 'staffkey', foreignKey: 'user_id'})
}
}
});
return Sshkey;
};
What would be a proper solution for the problem that if i later on want to reference a sshkey to either a user or a staff member?
Making two models with staffkeys and userkeys?
Thanks in advance,
BigZ
If you want a 1:m relationship where the foreign key user_id is added to the Sshkey model, should be:
User.hasMany(models.Sshkey, {foreignKey: 'user_id'});
Staff.hasMany(models.Sshkey, {foreignKey: 'user_id'});
Sshkey.belongsTo(models.User, {foreignKey: 'user_id'});
Sshkey.belongsTo(models.Staff, {foreignKey: 'user_id'});
One issue I have with your example is that user_id and sshkey_id are both strings with no constraints around them, which makes them very bad foreignKey's and terrible for designing databases. To get a User and their Sshkey:
User.findAll({
where: {},
include: [ { model: Sshkey } ]
});