Sequelize - Retrieve association after model creation - node.js

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).

Related

NodeJS - Sequelize how to declare association for eager loading only once to be used for queries later

The problem:
Whenever I fetch a user, I always have to declare/include the association on the query to get its role:
const user = await DB.PORTALDB.models.AccessUser.findOne({
where: { email },
include: [ // EVERY QUERY, I HAVE TO INCLUDE THIS
{
model: DB.PORTALDB.models.AccessUserRoleLup,
as: 'role'
}
]
});
Now there are instance where I forget to include this association so I get a undefined role.
My question is, is there a way where I only set this association once so that I don't have to include this later on my queries?
This the model for my AccessUser table
const AccessUser = <AccessUserStatic>sequelize.define<AccessUserInstance>(
'AccessUser',
{
user_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
email: {
type: DataTypes.STRING(255),
allowNull: false
},
firstname: {
type: DataTypes.STRING(255),
allowNull: false
},
lastname: {
type: DataTypes.STRING(255),
allowNull: false
},
password: {
type: DataTypes.STRING(255),
allowNull: false
},
disable: {
type: DataTypes.TINYINT,
allowNull: false,
defaultValue: 0
},
role_id: {
type: DataTypes.SMALLINT,
allowNull: false,
defaultValue: 0
},
created_modified: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
}
}, {
tableName: 'access_user',
timestamps: false,
indexes: [
{
name: "PRIMARY",
unique: true,
using: "BTREE",
fields: [
{ name: "user_id" },
]
},
]
});
AccessUserRoleLup table
const AccessUserRoleLup = <AccessUserRoleLupStatic>sequelize.define<AccessUserRoleLupInstance>(
'AccessUserRoleLup',
{
role_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
role_name: {
type: DataTypes.STRING(50),
allowNull: false
},
role_code: {
type: DataTypes.CHAR(50),
allowNull: false,
defaultValue: ""
}
}, {
tableName: 'access_user_role_lup',
timestamps: false,
indexes: [
{
name: "PRIMARY",
unique: true,
using: "BTREE",
fields: [
{ name: "role_id" },
]
},
]
});
Association:
db.models.AccessUser.hasOne(db.models.AccessUserRoleLup, {
foreignKey: 'role_id',
as: 'role'
});
Use defaultScope for AccessUser. defaultScope is defined in a model definition and it is always applied (unless you removed inline).
const AccessUser = <AccessUserStatic>sequelize.define<AccessUserInstance>(
'AccessUser',
{
user_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
...
}, {
tableName: 'access_user',
timestamps: false,
defaultScope: { // Add this
include: [{
model: AccessUserRoleLup,
as: 'role'
}]
},
...
});
With this model definition, all queries will include AccessUserRoleLup.
If you would like to remove for a certain query, use .unscoped().
// These will automatically add eager loading for role
DB.PORTALDB.models.AccessUser.findAll()
DB.PORTALDB.models.AccessUser.findOne()
// These won't fetch role
DB.PORTALDB.models.AccessUser.unscoped().findAll()
DB.PORTALDB.models.AccessUser.unscoped().findOne()
More detail about scope: https://sequelize.org/master/manual/scopes.html
My initial workaround was to create a utility function for querying the user like so:
export const getAccessUser = (where: WhereOptions, include?: IncludeOptions) => {
return new Promise(async (resolve, reject) => {
try {
const user = await DB.PORTALDB.models.AccessUser.findOne({
where: where,
include: [
{
model: DB.PORTALDB.models.AccessUserRoleLup,
as: 'role'
},
...[include]
]
});
resolve(user);
} catch (err) {
reject(err);
}
});
}
I wonder if my question above can be done in much simpler way.

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

Has one association in Sequelize

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.

Join two tables via a third table in sequelize

I'm using nodejs and Sequelize
I have two models like
user-plan.model.js
class UserPlan extends Sequelize.Model {
}
UserPlan.init({
id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true
},
user_id: {
type: Sequelize.INTEGER,
allowNull: false
},
plan_id: {
type: Sequelize.INTEGER,
allowNull: false
},
expire: {
type: Sequelize.DATE,
allowNull: true
},
active: {
type: Sequelize.BOOLEAN,
allowNull: true
}
}, {
sequelize,
modelName: 'user_plan',
tableName: 'plans_userplan'
});
quota.model.js
class Quota extends Model {
}
Quota.init({
id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true
},
codename: {
type: Sequelize.STRING,
allowNull: false
}
}, {
sequelize,
modelName: 'quota',
tableName: 'plans_quota'
});
These two tables are joined through a third table
plan-quota.model.js
class PlanQuota extends Model {
}
PlanQuota.init({
id: {
type: Sequelize.INTEGER,
primaryKey: true,
allowNull: false
},
value: {
type: Sequelize.INTEGER,
allowNull: false,
},
plan_id: {
type: Sequelize.STRING,
allowNull: false
},
quota_id: {
type: Sequelize.STRING,
allowNull: false
}
}, {
sequelize,
modelName: 'plan_quota',
tableName: 'plans_planquota'
});
The PlanQuota table has link to the UserPlan using plan_id and Quota table using quota_id.
User.findOne({
where: {
'id': user_id
},
include: [
{
model: UserPlan,
include: [
{
model: PlanQuota
}
]
}
]
}).then(d => {
console.log('d: ', d);
});
The UserPlan is associated with User model, and I'm able to include the UserPlan using User.
But how Can I include PlanQuota and the Quota using the join?
I think you need to "include" the associated model, not the junction table:
User.findOne({
where: {
'id': user_id
},
include: [
{
model: UserPlan,
include: [
{
model: Qouta//This is the change
}
]
}
]
}).then(d => {
console.log('d: ', d);
});
Do you have your associations setup? like:
UserPlan.belongsToMany(Qouta, { through: PlanQuota });
Qouta.belongsToMany(UserPlan, { through: PlanQuota });

nodejs sequelize join 2 table using hasmany

I am new in nodejs. I am creating new app using nodejs. i want to join two table city and state using hasmany relations.
Here is my state model state.js
module.exports = function(sequelize, DataTypes) {
return sequelize.define('state', {
name: {
type: DataTypes.STRING(255),
allowNull: true
},
id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
createdAt: {
type: DataTypes.DATE,
allowNull: true
},
updatedAt: {
type: DataTypes.DATE,
allowNull: true
}
}, {
tableName: 'state'
});
};
Here is my city model city.js
module.exports = function(sequelize, DataTypes) {
return sequelize.define('city', {
state: {
type: DataTypes.INTEGER(11),
allowNull: true
},
name: {
type: DataTypes.STRING(255),
allowNull: true
},
id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
createdAt: {
type: DataTypes.DATE,
allowNull: true
},
updatedAt: {
type: DataTypes.DATE,
allowNull: true
}
}, {
tableName: 'city'
});
};
This is my citycontroller.js
let City = require('../models').city;
let State = require('../models').state;
let message = require('../../config/message');
let Boom = require('boom');
module.exports = {
listCity: async(request,h) =>{
let stateid = request.query.stateid;
try{
let searchQuery = {};
if(stateid) searchQuery.state = stateid;
let listCity = await City.findAll({ where:searchQuery});
if(listCity.length){
let response = {
"statusCode": 200,
"message":message.DATAFOUND,
"result": listCity
};
return h.response(response).code(200);
}else{
return h.response(response).code(204);
}
}catch(err){
return Boom.badRequest(err.message);
}
},
};
Output:
{
"statusCode": 200,
"message": "Data found",
"result": [
{
"state": 1,
"name": "Los Angeles",
"id": 1,
"createdAt": null,
"updatedAt": null
},
{
"state": 1,
"name": "San Francisco",
"id": 2,
"createdAt": null,
"updatedAt": null
},
{
"state": 5,
"name": "Southhampton",
"id": 3,
"createdAt": null,
"updatedAt": null
}
]
}
Now it listing city details only. but i need to join state details also under each city.
If you want to select the data from the state table than define a relation in the state model.
module.exports = function(sequelize, DataTypes) {
const state = sequelize.define('state', {
name: {
type: DataTypes.STRING(255),
allowNull: true
},
id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
createdAt: {
type: DataTypes.DATE,
allowNull: true
},
updatedAt: {
type: DataTypes.DATE,
allowNull: true
}
}, {
tableName: 'state'
});
state.associate = function(model){
models.state.hasMany(models.city){
foreignKey: 'state'
}
return state;
}
};
Or if you want to select data from the city table than define a similar hasOne relation in city table.

Resources