Has one association in Sequelize - node.js

I have a User Model with a hasOne relation on Role Model
User.init({
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true
},
name: {
type: DataTypes.STRING,
allowNull: false
},
//email, password, and other fields, ...
roleId: {
type:DataTypes.INTEGER,
allowNull: false
}},
{
sequelize,
tableName: "Users"
});
User.hasOne(Role)
and a Role Model
Role.init({
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true
},
name: {
type: DataTypes.STRING,
allowNull: false
}},
{
sequelize,
tableName: "Roles"
});
When I try to create a new Role with
await Role.create(req.body)
And the request is
POST http://localhost:3000/api/role
Content-Type: application/json
Authorization: Bearer <token>
{
"name": "test role"
}
I get the error column "UserId" does not exist
And the log says is:
routine: 'errorMissingColumn',
sql: 'INSERT INTO "Roles" ("id","name") VALUES (DEFAULT,$1) RETURNING "id","name","UserId";',
parameters: [
'test role'
]
What did I do wrong here? My table only has roleId in the Users table, where did the UserId in Roles table come from?
Migrations
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Roles', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING,
allowNull: false
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Roles');
}
};
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
// other fields
roleId: {
type: Sequelize.INTEGER,
references: {
model: "Roles",
key: "id"
}
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Users');
}
};
If I add Role.belongsTo(User) in the Role Model, I get the error:
models init error: TypeError: Cannot read property 'name' of undefined

For the role to be stored in the user table as your schema suggests: User.belongsTo(Role) will setup the mapping for you as RoleId.
The Model sets up the foreign key the opposite way to the migrations, so results in a missing UserID column.
The foreign keys don't need to be defined in the schema unless you want to customise the fields. The belongsTo/hasOne options are then defined on association call.
const { Sequelize, Model, DataTypes } = require('sequelize')
const sequelize = new Sequelize('sqlite::memory:')
class User extends Model {}
class Role extends Model {}
User.init({
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true
},
name: {
type: DataTypes.STRING,
allowNull: false
}},
{
sequelize,
tableName: "Users"
});
Role.init({
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true
},
name: {
type: DataTypes.STRING,
allowNull: false
}},
{
sequelize,
tableName: "Roles"
});
User.belongsTo(Role, { foreignKey: 'roleId' })
Then you can do things with the association
async function go(){
await sequelize.sync()
const role = await Role.create({ name: 'atester' })
const user = await User.create({ name: 'test' })
await user.setRole(role)
console.log("%j", await User.findAll({ include: Role }))
}
go().catch(console.error)
Results in a document like:
{
"id": 1,
"name": "test",
"createdAt": "2020-12-04T09:44:05.762Z",
"updatedAt": "2020-12-04T09:44:05.763Z",
"roleId": 1,
"Role": {
"id": 1,
"name": "atester",
"createdAt": "2020-12-04T09:44:05.758Z",
"updatedAt": "2020-12-04T09:44:05.758Z"
}
}
From there you can match your migration to the database.

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 fetch include data based on alias where condition

I have two tables Employee and Department
Department
const Department = Sequelize.define(
"Department",
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
underscored: true,
timestamps: true,
paranoid: true,
modelName: "Department",
tableName: "departments",
},
);
Department.associate = function (models) {
// associations can be defined here
models.Department.hasMany(models.Employee, {
foreignKey: "department_id",
as: "employees",
});
};
return Department;
};
Employee
const Employee = Sequelize.define(
"Employee",
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
},
status: {
type: DataTypes.STRING,
defaultValue: "active",
},
departmentId: {
type: DataTypes.INTEGER,
},
},
{
underscored: true,
timestamps: true,
modelName: "Employee",
tableName: "employees",
},
);
Employee.associate = function (models) {
models.Employee.belongsTo(models.Department, {
foreignKey: "department_id",
as: "department",
});
};
return Employee;
};
Now I have to fetch the list of employees and putting a filter of department_id = 1
const { departmentId } = req.body;
const employees = await Employee.findAll({
include: [
{
model: Department,
where: {
id: departmentId,
},
},
],
});
I am getting the issue. Department is mapped by association "departments"
Cannot fetch the data.
I found the answer on sequelize docs
const employees = await Employee.findAll({
include: [
{
association: "department", // this is the place to change
where: {
id: departmentId,
},
},
],
});
Learnings:
We will not be able to put association and model together.
We will be able to use the Model if no association is there.
We will be able to use association if there is one.
References: https://sequelize.org/master/manual/eager-loading.html#:~:text=You%20can%20also%20include%20by%20alias%20name%20by%20specifying%20a%20string%20that%20matches%20the%20association%20alias

Sequelize - Retrieve association after model creation

I am creating a model through a relation, I would like to know if it is possible to obtain the relation in the return of the model.
Company Model
module.exports = (sequelize) => {
const company = sequelize.define('company', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
uuid: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
},
name: {
type: DataTypes.STRING,
allowNull: false
}
})
company.hasMany(sequelize.models.flow, {foreignKey: 'company_id', as: 'flows'})
}
Flow model
module.exports = (sequelize) => {
const flow = sequelize.define('flow', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
company_id: {
allowNull: false,
type: DataTypes.INTEGER
},
uuid: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
},
name: {
type: DataTypes.STRING,
allowNull: false
},
description: {
type: DataTypes.TEXT
}
})
flow.belongsTo(sequelize.models.company, {foreignKey: 'company_id', as: 'company'})
}
Query
const company = await ORM.models.company
.findOne({
where: {
uuid: req.body.company_id
}
})
if (company) {
const flow = await company.createFlow({
name: req.body.name
})
return res.json(flow)
}
I am currently getting the following response:
{
"uuid": "647aa7b2-163a-4bab-80f6-441c9bf29915",
"id": 12,
"name": "Flow 2",
"company_id": 2,
"updated_at": "2021-02-11T06:08:25.160Z",
"created_at": "2021-02-11T06:08:25.160Z",
"description": null
}
I would like to obtain:
{
"uuid":"647aa7b2-163a-4bab-80f6-441c9bf29915",
"id":12,
"name":"Flow 2",
"updated_at":"2021-02-11T06:08:25.160Z",
"created_at":"2021-02-11T06:08:25.160Z",
"description":null,
"company":{
"id":2,
"uuid":"3dea2541-a505-4f0c-a356-f1a2d449d050",
"name":"Company 1",
"created_at":"2021-02-11T05:48:11.872Z",
"updated_at":"2021-02-11T05:48:11.872Z"
}
}
It is possible?
Because you are not attaching company data with the result JSON data structure, so you are getting only flow data.
To get the expected result please try to modify the JSON structure as follows:
flow.company = company;
just before the return res.json(flow).

Sequelize foreign key with association

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
}

Resources