Sequelize foreign key with association - node.js

I have a database that was created with Postgres that was set up for a single foreign key association, Now, this would be mapped as a role table model
consider I have two tables user and roles
roles contain role details and user contain user details of role
const uuid = require('uuid/v4');
('use strict');
module.exports = (sequelize, DataTypes) => {
const role = sequelize.define(
'role',
{
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
},
{}
);
role.beforeCreate((role) => (role.id = uuid()));
role.associate = function (models) {
role.hasMany(models.user), { foreignKey: 'roleId', as: 'user_roleId' };
};
return role;
};
role migration
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('roles', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
},
name: {
type: Sequelize.STRING,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('roles');
},
};
user model
const uuid = require('uuid/v4');
('use strict');
module.exports = (sequelize, DataTypes) => {
const user = sequelize.define(
'user',
{
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
},
firstName: {
type: DataTypes.STRING,
allowNull: false,
},
lastName: DataTypes.STRING,
email: {
type: DataTypes.STRING,
allowNull: false,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
phoneNumber: {
type: DataTypes.STRING,
},
roleId: {
type: DataTypes.UUID,
},
},
{
timestamps: true,
paranoid: true,
defaultScope: {
attributes: { exclude: ['password'] },
},
}
);
user.beforeCreate((user) => (user.id = uuid()));
user.associate = function (models) {
user.belongsTo(models.role, { foreignKey: 'roleId', onDelete: 'CASCADE' });
};
return user;
};
user migration
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('users', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
},
firstName: {
type: Sequelize.STRING,
},
lastName: {
type: Sequelize.STRING,
},
email: {
type: Sequelize.STRING,
},
password: {
type: Sequelize.STRING,
},
phoneNumber: {
type: Sequelize.STRING,
},
roleId: {
type: Sequelize.UUID,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
deletedAt: {
allowNull: true,
type: Sequelize.DATE,
},
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('users');
},
};
after running the migration these tables are created in my database.role_id is also present in the user table. but role_id is not generated as a foreign key in my user table. also please verify that the relationship which is mention here(one to many) is correct or not.
please verify my code and give me any suggestions if any changes required. I'm new in development

Your user migration also needs to know about the foreign key; you do this by adding a references: key to the column definition. The Sequelize documentation has a foreign key example; scroll about half way down the page (or just search for references).
In your case the user migration should look something like:
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('users', {
// ... other fields omitted
roleId: {
type: Sequelize.UUID,
references: {
model: { tableName: 'role' }
key: 'id',
},
},
// ... more fields omitted
});
},
// down: omitted
}

Related

Running migration addConstraint Foreign Key not on Primary Key fields causes SQLite Error

I am trying to configure a Foreign Key association between two tables on 'non-PrimaryKey' fields for one-to-many relation:
Asset.belongsTo(AssetClass)
AssetClass.hasMany(Asset)
I create tables first and add the constraint in the third migration:
migrations\20220621223626-create-asset.js
'use strict';
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('Assets', {
ticker: {
allowNull: false,
autoIncrement: false,
primaryKey: true,
type: Sequelize.STRING
},
shortName: {
allowNull: false,
type: Sequelize.STRING
},
fullName: {
allowNull: false,
type: Sequelize.STRING
},
assetClass: {
allowNull: false,
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable('Assets');
}
};
migrations\20220622035610-create-asset-class.js
'use strict';
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('AssetClasses', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING,
allowNull: false
},
prio: {
type: Sequelize.INTEGER,
allowNull: false
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable('AssetClasses');
}
};
migrations\20220627211055-add-constraint-fk_asset-assetClass.js
'use strict';
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.addConstraint('Assets', {
fields: ['assetClass'], //existing field in Assets table
type: 'foreign key',
name: 'fk_asset-assetClass',
references: {
table: 'AssetClasses', //reference to AssetClasses table
field: 'name' //name of the target field
}
});
},
async down(queryInterface, Sequelize) {
await queryInterface.removeConstraint('Assets', 'fk_asset-assetClass');
}
};
After running db::migrate I am getting a following error message:
SQLITE_ERROR: foreign key mismatch - "Assets_backup" referencing "AssetClasses"
which leaves me with a Assets_backup table in the database which I need to remove manually.
What seems to works though is:
Creating a new column assetClassId in Assets table and referencing it to Primary Key field (id) of AssetClasses table:
//addConstraint migration
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.addConstraint('Assets', {
fields: ['assetClassId'], //existing field in Assets table
type: 'foreign key',
name: 'fk_asset-assetClass',
references: {
table: 'AssetClasses', //reference to AssetClasses table
field: 'id' //name of the target field
}
});
},
async down(queryInterface, Sequelize) {
await queryInterface.removeConstraint('Assets', 'fk_asset-assetClass');
}
};
//createTable Assets migration
assetClassId: {
allowNull: false,
type: Sequelize.INTEGER
},
How can I make it work for existing non-PK fields?

postgres returns another table column while inserting data in sequelize

When I try to insert new category, I got this error:
error: column "image" does not exist
sql: 'INSERT INTO "Categories" ("id","createdAt","updatedAt") VALUES (DEFAULT,$1,$2) RETURNING "id","image","title","createdAt","updatedAt";'
The problem is that it doesn't insert name and other values and returns columns belong to post table.
My guesses are the problem of sequelize-cli and sequelize version or missing something in models or migrations.
I only insert values into name, createdAt and updatedAt column:
await Category.create({
name: req.body.name,
createdAt: new Date(),
updatedAt: new Date()
});
My category model:
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class Category extends Model {
static associate(models) {
Category.hasMany(models.Post, { as: "posts", foreignKey: "categoryId" });
}
}
Category.init(
{
name: DataTypes.STRING
},
{
sequelize,
modelName: "Category"
}
);
return Category;
};
My Post Model:
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class Post extends Model {
static associate(models) {
Post.belongsTo(models.Category, { foreignKey: "categoryId", onDelete: "CASCADE", as: "category" });
}
}
Post.init(
{
title: DataTypes.STRING,
image: DataTypes.STRING,
content: DataTypes.TEXT,
categoryId: DataTypes.INTEGER
},
{
sequelize,
modelName: "Post"
}
);
return Post;
};
Post migration:
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable("Posts", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
title: {
type: Sequelize.STRING
},
image: {
type: Sequelize.STRING
},
content: {
type: Sequelize.TEXT
},
categoryId: {
type: Sequelize.INTEGER,
allowNull: false,
onDelete: "CASCADE",
references: {
model: "Categories",
key: "id"
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
Category migration:
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable("Categories", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
I couldn't find solution for this, therefor I used sequelize.query

Sequelize Node Js Seeder file with Many-to-Many Association

I would like to find out what is the right way to add a many-to-many relation when seeding records with sequelize-cli. Now I understand that the easiest way to do this is to create another seeder file and manually set the values but is there a way to make some changes to my 2_user seeder file so that when a user is seeded with some value given for the role it automatically makes a record in the user_roles table. So basically same as the user.setRoles() one can use but in the seeder files. Any help is much appreciated.
Models
user.model.js
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class User extends Model {
static associate(models) {
User.belongsToMany(models.Role, {
through: 'user_roles',
foreignKey: 'user_id',
otherKey: 'role_id',
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
});
User.hasMany(models.User_Role);
User.hasMany(models.Address, {
foreignKey: 'user_id',
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
});
}
}
User.init(
{
user_id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
email: DataTypes.STRING,
password: DataTypes.STRING,
bio: DataTypes.STRING,
activity_status: DataTypes.BOOLEAN,
},
{
sequelize,
modelName: 'User',
}
);
return User;
};
role.model.js
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class User extends Model {
static associate(models) {
User.belongsToMany(models.Role, {
through: 'user_roles',
foreignKey: 'user_id',
otherKey: 'role_id',
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
});
User.hasMany(models.User_Role);
User.hasMany(models.Address, {
foreignKey: 'user_id',
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
});
}
}
User.init(
{
user_id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
email: DataTypes.STRING,
password: DataTypes.STRING,
bio: DataTypes.STRING,
activity_status: DataTypes.BOOLEAN,
},
{
sequelize,
modelName: 'User',
}
);
return User;
};
user_role.model
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class User_Role extends Model {
static associate(models) {
User_Role.belongsTo(models.User, {
foreignKey: 'user_id',
});
User_Role.belongsTo(models.Role, {
foreignKey: 'role_id',
});
}
}
User_Role.init(
{
user_role_id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
},
{
sequelize,
modelName: 'User_Role',
}
);
return User_Role;
};
Migrations
1-create-user
'use strict';
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('users', {
user_id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
email: {
type: Sequelize.STRING,
unique: true,
},
password: {
type: Sequelize.STRING,
},
bio: {
type: Sequelize.STRING,
},
activity_status: {
type: Sequelize.BOOLEAN,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
});
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable('users');
},
};
3-create-role
'use strict';
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('roles', {
role_id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
unique: true,
type: Sequelize.INTEGER,
},
user_type: {
type: Sequelize.STRING,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
});
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable('roles');
},
};
6-create-user_role
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('user_roles', {
user_role_id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
user_id: {
type: Sequelize.INTEGER,
references: { model: 'users', key: 'user_id' },
onDelete: 'CASCADE',
},
role_id: {
type: Sequelize.INTEGER,
references: { model: 'roles', key: 'role_id' },
onDelete: 'CASCADE',
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
});
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable('user_roles');
},
};
Seeders
1_role
'use strict';
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.bulkInsert(
'roles',
[
{
user_type: 'courier',
createdAt: new Date(),
updatedAt: new Date(),
},
{
user_type: 'receiver',
createdAt: new Date(),
updatedAt: new Date(),
},
{
user_type: 'donor',
createdAt: new Date(),
updatedAt: new Date(),
},
{
user_type: 'admin',
createdAt: new Date(),
updatedAt: new Date(),
},
],
{}
);
},
async down(queryInterface, Sequelize) {
await queryInterface.bulkDelete('Roles', null, {});
},
};
2_user
('use strict');
var bcrypt = require('bcryptjs');
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.bulkInsert(
'users',
[
{
email: 'courier#email.com',
password: bcrypt.hashSync('PassWord123#', 10),
bio: 'This is a courier type user!',
activity_status: true,
createdAt: new Date(),
updatedAt: new Date(),
role_id: 1,
},
{
email: 'donor#email.com',
password: bcrypt.hashSync('PassWord123#', 10),
bio: 'This is a donor type user!',
activity_status: true,
createdAt: new Date(),
updatedAt: new Date(),
},
{
email: 'receiver#email.com',
password: bcrypt.hashSync('PassWord123#', 10),
bio: 'This is a Receiver type user!',
activity_status: true,
createdAt: new Date(),
updatedAt: new Date(),
},
{
email: 'admin#email.com',
password: bcrypt.hashSync('PassWord123#', 10),
bio: 'This is a Admin type user!',
activity_status: true,
createdAt: new Date(),
updatedAt: new Date(),
},
],
{}
);
},
async down(queryInterface, Sequelize) {
await queryInterface.bulkDelete('Users', null, {});
},
};

Sequelize query singular instead of plural table name

I have an problem and I can't find anything that can solve it. I'm using sequelize and graphql to create an API in nodeJS. The database is using PostgresQL.
So I have two models: Simulation and Data. They are in two tables Simulations and Datas. The relation between them is one Simulation to many Datas.
The problem is this: when I make a query with Simulation (ex: Simulation.findAll()), it works correctly, querying "Simulations", but with Data, it queries on the "Data" table, not "Datas". What I really don't understand is that the code of my two models are almost the same.
Here is the model for Simulation:
module.exports = (sequelize, DataTypes) => {
const Simulation = sequelize.define('Simulation', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
});
Simulation.associate = function(models) {
Simulation.hasMany(models.Data, {
foreignKey: 'SimulationId',
})
};
return Simulation;
};
Here is the model for Data:
module.exports = (sequelize, DataTypes) => {
const Data = sequelize.define('Data', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER,
},
name: {
type: DataTypes.STRING,
allowNull: false
},
content: {
type: DataTypes.TEXT,
allowNull: false
},
SimulationId: {
allowNull: false,
type: DataTypes.INTEGER,
},
});
Data.associate = function(models) {
Data.belongsTo(models.Simulation, {
foreignKey: 'SimulationId',
targetKey: 'id',
allowNull: false,
onDelete: 'CASCADE'
});
};
return Data;
};
And here are the migration files:
Simulation
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Simulations', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING,
allowNull: false,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Simulations');
}
};
Data
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Datas', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING,
allowNull: false
},
content: {
type: Sequelize.TEXT,
allowNull: false
},
SimulationId: {
allowNull: false,
type: Sequelize.INTEGER,
onDelete: 'CASCADE',
references: {
model: 'Simulation',
key: 'id',
as: 'SimulationId',
},
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Datas');
}
};
Thanks for helping me :)
You can use freezeTableName option to set whatever the model name you want, sequelize will not make the model names plural.
Sequelize automatically makes the model names plural. Why not call the table "Data" It is actually a plural form of the word "Data", so maybe a better name for the table.

Sequelize.js still deletes table row even if paranoid is set to true

I'm having trouble getting Sequelize.js to soft delete the rows in my table. I used Sequelize cli to do all my migrations and I'm not using the sync feature to resync the database on start. I have the timestamp fields and even the deletedAt field in my migration and models (model has paranoid: true also) and no matter what it still deletes the row instead of adding a timestamp to the deletedAt field. I noticed when do any querying it doesn't add the deletedAt = NULL in the query like I've seen in some tutorials. I'm using Sequelize.js v3.29.0.
Model File:
'use strict';
module.exports = function(sequelize, DataTypes) {
var Collection = sequelize.define('Collection', {
userId: {
type: DataTypes.INTEGER,
allowNull: false,
validate: {
isInt: true
}
},
name: {
type: DataTypes.STRING,
allowNull: false
},
description: DataTypes.TEXT,
createdAt: {
allowNull: false,
type: DataTypes.DATE
},
updatedAt: {
allowNull: false,
type: DataTypes.DATE
},
deletedAt: {
type: DataTypes.DATE
}
}, {
classMethods: {
associate: function(models) {
Collection.belongsTo(models.User, { foreignKey: 'userId' })
}
}
}, {
timestamps: true,
paranoid: true
});
return Collection;
};
Migration File:
'use strict';
module.exports = {
up: function(queryInterface, Sequelize) {
return queryInterface.createTable('Collections', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
userId: {
allowNull: false,
type: Sequelize.INTEGER
},
name: {
allowNull: false,
type: Sequelize.STRING
},
description: {
type: Sequelize.TEXT
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
},
deletedAt: {
type: Sequelize.DATE
}
});
},
down: function(queryInterface, Sequelize) {
return queryInterface.dropTable('Collections');
}
};
Here is the code in the controller I'm using to destroy the collection object.
Collection.findOne({
where: {
id: collectionId,
userId: user.id
}
}).then(function(collection){
if (collection !== null) {
collection.destroy().then(function(){
res.redirect('/collection');
}).catch(function(error){
res.redirect('/collection/'+collectionId);
});
}
});
Make sure paranoid is attribute defined inside second object param.
..., {
classMethods: {
associate: function(models) {
Collection.belongsTo(models.User,{ foreignKey: 'userId' })
}
},
timestamps: true,
paranoid: true
}
You've defined paranoid as 3. Param and that is the problem.

Resources