Sequelize many to many relationship - node.js

I need some help to get my head around the sequelize many to many relationship.
I have to models, clients and services (many to many). Now I would like to load all services offered by one client (via client id). How does the condition with include have to look like to achieve this?
Service:
var Service = sequelize.define('service', {
title: {
type: DataTypes.STRING(200),
allowNull: false
}
},{
paranoid: true,
underscored: true,
classMethods: {
associate:function(models){
Service.belongsToMany(models.client, { through: 'client_services' });
Service.hasMany(models.rule, { foreignKey: 'service_id' })
}
}
});
Client:
var Client = sequelize.define('client', {
title: {
type: DataTypes.STRING(200),
allowNull: false
},
company: {
type: DataTypes.STRING(200),
allowNull: false
},
vendor: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
consumer: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true
},
address_id: {
type: DataTypes.INTEGER,
allowNull: true
}
},{
paranoid: true,
underscored: true,
classMethods: {
associate:function(models){
Client.hasMany(models.service, { through: 'client_services'});
}
}
});
In my Controller (doesn't work err: [Error: client (client) is not associated to service!]):
var condition = {
where: { 'client.id': req.user.client_id },
include: [{ model: Client, as: 'client' }]
}
Service.findAll(condition)
.then(responseWithResult(res))

Ok the where clause needs to be in the include with the model:
var condition = {
include: [{ model: Client, where: { 'id': req.user.client_id } }]
}
Service.findAll(condition)
.then(responseWithResult(res))

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: Association with alias does not exist

I am using sequelize as ORM in a node project and running into some issues when querying.
Below is the code for the sequelize models in question. The table names are singular--see the use of freezable: true in the model. But when I use include it does not work and fails with the following error message
Error: Association with alias "Client" does not exist on Invoice
Model - client
module.exports = (sequelize, DataTypes) => {
const Client = sequelize.define("Client", {
Id: {
primaryKey: true,
type: "INTEGER",
autoIncrement:true
},
Name:{
type: "VARCHAR(250)",
allowNull: false,
validate: {
notEmpty: true
}
},
AddressLine1:{
type: "VARCHAR(500)",
allowNull: false,
validate: {
notEmpty: true
}
},
AddressLine2:{
type: "VARCHAR(500)",
allowNull: true,
},
AddressLine3:{
type: "VARCHAR(500)",
allowNull: true,
},
Postcode:{
type: "VARCHAR(10)",
allowNull: false,
validate: {
notEmpty: true
}
},
City:{
type: "VARCHAR(100)",
allowNull: true,
},
County:{
type: "VARCHAR(50)",
allowNull: true,
},
Country:{
type: "VARCHAR(100)",
allowNull: true,
},
ContactNumber : {
type: "VARCHAR(20)",
allowNull: true,
},
Email : {
type: "VARCHAR(500)",
allowNull: true,
},
CreatedAt :{
type:"datetimeoffset(7) DEFAULT GETDATE()",
allowNull: false
},
UpdatedAt :{
type:"datetimeoffset(7)",
allowNull: true
}
},
{freezeTableName: true, createdAt: false,updatedAt: false}
);
Client.associate=models=>{
Client.hasMany(models.Invoice,{foreignKey:"ClientId"})
}
return Client;
}
Model Invoice
module.exports = (sequelize, DataTypes) => {
const Invoice = sequelize.define("Invoice", {
Id: {
primaryKey: true,
type: "INTEGER",
autoIncrement:true
},
ClientId: {
type: DataTypes.INTEGER,
},
CompanyId: {
type: DataTypes.INTEGER,
},
InvoiceNumber:{
type: "VARCHAR(10)",
allowNull: false,
validate: {
notEmpty: true
}
},
InvoiceDate : {
type: DataTypes.DATEONLY,
},
Total: {
type: DataTypes.DECIMAL(18,2),
allowNull: true
},
CreatedAt :{
type:"datetimeoffset(7) DEFAULT GETDATE()",
allowNull: false
},
UpdatedAt :{
type:"datetimeoffset(7)",
allowNull: true
}
},
{freezeTableName: true, createdAt: false,updatedAt: false} );
Invoice.associate=models=>{
Invoice.belongsTo(models.Client,{
foreignKey:{
name:"ClientId",
allowNull:false
}
})
};
Invoice.associate=models=>{
Invoice.belongsTo(models.Company,{
foreignKey:{
name:"CompanyId",
allowNull:false
}
})
}
Invoice.associate=models=>{
Invoice.hasMany(models.InvoiceDetails,{foreignKey:"InvoiceId"})
}
return Invoice;
}
Query
let invoiceResult= await db.Invoice.findOne({where: {"InvoiceNumber":"INV001"}
,include:"Client"});
The above query does not work. However, the following one works
let invoiceResult= await db.Invoice.findOne({where: {"InvoiceNumber":"INV001"}
,include:"InvoiceDetails"});
The model for InvoiceDetails is as follows
module.exports = (sequelize, DataTypes) => {
const InvoiceDetails = sequelize.define("InvoiceDetails", {
Id: {
primaryKey: true,
type: "INTEGER",
autoIncrement:true
},
InvoiceId: {
type: DataTypes.INTEGER,
},
Description:{
type: DataTypes.STRING(500),
allowNull: false,
validate: {
notEmpty: true
}
},
Quantity:{
type: DataTypes.INTEGER,
allowNull: false,
validate: {
notEmpty: true
}
},
AmountPerUnit : {
type: DataTypes.DECIMAL(18,2),
allowNull: false,
validate: {
notEmpty: true
}
},
SubTotal: {
type: DataTypes.DECIMAL(18,2),
allowNull: false,
validate: {
notEmpty: true
}
},
VatPercentage: {
type: DataTypes.DECIMAL(5,2),
allowNull: false,
validate: {
notEmpty: true
}
},
VatAmount: {
type: DataTypes.DECIMAL(18,2),
allowNull: false,
validate: {
notEmpty: true
}
}
,
CreatedAt :{
type:"datetimeoffset(7) DEFAULT GETDATE()",
allowNull: false
},
UpdatedAt :{
type:"datetimeoffset(7)",
allowNull: true
}
},
{freezeTableName: true, createdAt: false,updatedAt: false}
);
InvoiceDetails.associate=models=>{
InvoiceDetails.belongsTo(models.Invoice,{
foreignKey:{
name:"InvoiceId",
allowNull:false
}
})
}
return InvoiceDetails;
}
Any pointers will be much appreciated.
i fixed it with the following changes
in the client model changed the following
Client.associate=models=>{
Client.hasMany(models.Invoice,{as:'Invoice',foreignKey:"ClientId"})
}
and in the invoice model
Invoice.associate=models=>{
Invoice.belongsTo(models.Client,{
as: 'Client',
foreignKey:{
name:"ClientId",
allowNull:false
}
})
};
After this change both the following queries work. The point being when using include as alias needs to be present in both the models. Atleast that's how I got it working.
let invoiceResult= await db.Invoice.findOne({where: {"InvoiceNumber":invoiceNumber}
,include:[
"Client"
]
});
let client= await db.Client.findAll({include:[
"Invoice"
]
});
Thanks for the help and taking the time to post an answer
I think this two way can help you
1:
Sequelize pluralized models try "Clients" instead
let invoiceResult= await db.Invoice.findOne({where: {"InvoiceNumber":"INV001"}
,include:"Clients"});
2: if "Clients" not work try this
let invoiceResult= await db.Invoice.findOne({where: {"InvoiceNumber":"INV001"}
,include:[{model: Client, as: "client"}]});
When using the "include" option in a query, you need to include a model object and not just the name of the table/model.
As an example I'll use a mock controller "InvoiceController.js":
const {Invoice} = require("Path to the invoice model") // or = 'db.Invoice'
const {Client} = require("Path to the client model") // or '= db.Client'
module.exports = {
async indexInvoiceWithClient (req, res) {
try {
let invoiceResults = await Invoice.findOne({
where: {
InvoiceNumber: "INV001" // req.query.invoiceNum if you request it over http/s
},
include: {
model: Client
}
})
console.log(invoiceResults)
res.status(200).send(invoiceResults)
} catch (err) {
console.log(err)
res.status(500).send({
error: "An error occured while fetching the clients on this Invoice etc..."
})
}
}
}

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 });

Node js and Sequelize insert to database

In my project I am using MySQL database and Sequelize Js,
I have two models created:
Post code model:
module.exports = function(sequelize, Sequelize) {
var Post_code = sequelize.define('post_code', {
id: {
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER(11)
},
code: {
type: Sequelize.STRING(16),
allowNull: false,
unique: true
},
city: {
type: Sequelize.STRING(45),
allowNull: false
}
},{
freezeTableName: true,
underscored: true
});
Post_code.associate = function(models) {
models.post_code.hasMany(models.building);
};
return Post_code;
}
Building model:
module.exports = function(sequelize, Sequelize) {
var Building = sequelize.define('building', {
id: {
autoIncrement: true,
primaryKey: true,
allowNull: false,
type: Sequelize.INTEGER(11)
},
name: {
type: Sequelize.STRING(100),
allowNull: false
},
number: {
type: Sequelize.INTEGER(11),
allowNull: false
},
address: {
type: Sequelize.STRING(255),
allowNull: false
},
latitude: {
type: Sequelize.DECIMAL(9,6),
allowNull: false
},
longitude: {
type: Sequelize.DECIMAL(9,6),
allowNull: false
}
},{
freezeTableName: true,
underscored: true
});
Building.associate = function (models) {
models.building.belongsTo(models.post_code, {
foreignKey: {
allowNull: false
}
});
models.building.hasMany(models.flat);
};
return Building;
};
They are in relation one to many, it follows that Post code has many Buildings:
I want to add new building to database, when POST request is send to this route:
"/post-codes/:post_code_id/buildings"
I have access to post_code_id but I don't know how to correctly associate post_code model with building.
I was trying to do something like this:
models.post_code.findById(req.params.post_code_id)
.then(function(postCode){
postCode.setBuilding(
models.building.create({
}).then(function(building){});
);
});
But without result. I would be grateful if someone could explain how to make inserts correctly.
I haven't heard about the include option on create, but you can do something like this:
db.post_code.find({
where: {
id: req.params.post_code_id
}
}).then(post_code => {
if (!post_code) {
return res.status(404).send({
message: 'No post_code with that identifier has been found'
});
} else {
//create here your building
}
}).catch(err => {
return res.jsonp(err)
});
I don't know if this is the best way to do it, but it verify first if the post_code exists.

Sequelize saving many to many

I was wandering if there are any extended tutorials on how to save a many to many relationship? I find the documentation more than basic. Its missing many use case examples.
I have two models: Client and Rule. They have the n:n relationship.
Client:
var Client = sequelize.define('client', {
title: {
type: DataTypes.STRING(200),
allowNull: false
},
company: {
type: DataTypes.STRING(200),
allowNull: false
},
vendor: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
consumer: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true
},
address_id: {
type: DataTypes.INTEGER,
allowNull: true
}
},{
paranoid: true,
underscored: true,
classMethods: {
associate:function(models){
Client.hasMany(models.rule, { through: 'client_rules', onDelete: 'cascade'});
}
}
});
Rule:
var Rule = sequelize.define('rule', {
service_id: {
type: DataTypes.INTEGER,
allowNull: false
},
is_allowed: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
valid_until: {
type: DataTypes.DATE,
allowNull: true,
},
rule: {
type: DataTypes.TEXT,
allowNull: true
},
type: {
type: DataTypes.INTEGER, // 1 for company rule, 2 for individual rule
allowNull: false,
},
active: {
type: DataTypes.BOOLEAN,
defaultValue: true
}
},{
underscored: true,
paranoid: true,
classMethods: {
associate:function(models){
Rule.belongsToMany(models.client, { through: 'client_rules', onDelete: 'cascade'});
Rule.belongsTo(models.service, { foreignKey: 'service_id' } );
}
}
});
Now I would like to create a new rule for client. So I would have to create the rule first and associate it then to the client through 'client_rules'.
How do I do that with sequelize? This doesn't work:
var clientID = req.user.client_id;
Client.find({ id: clientID })
.then(function(client){
return client.addRule(req.body)
})
.catch(function(err){
console.log(err)
})
[TypeError: Cannot read property 'replace' of undefined]
Ok I found out how to do it. The docs are very confusing.
var clientID = req.user.client_id;
return Rule.create(req.body)
.then(function(newRule){
var ruleToAdd = newRule;
return Client.findOne({ where: { id: clientID } })
.then(function(client){
return client.addRule(ruleToAdd)
.then(function(ans){
return ruleToAdd;
})
})

Resources