Duplicate data between models and migrations - node.js

I'm learning Sequelize and there is something that I found quite strange, so I think that I'm doing something wrong.
This is my migration for a simple Posts table :
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Posts', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
title: {
type: Sequelize.STRING,
allowNull: false,
},
content: {
type: Sequelize.TEXT,
allowNull: false,
},
authorId: {
type: Sequelize.INTEGER,
onDelete: 'CASCADE',
allowNull: false,
references: {model: 'Users', key: 'id'}
},
publishedAt: {
type: Sequelize.DATE,
allowNull: true
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Posts');
}
};
Another little question here, do I have to specify allowNull: false for the title and the content if I don't want them to be null. I think yes, but many projects I saw don't specify it.
This is the Post model :
'use strict';
module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define('Post', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
title: {
type: DataTypes.STRING,
allowNull: false,
},
content: {
type: DataTypes.TEXT,
allowNull: false,
},
publishedAt: {
type: DataTypes.DATE,
allowNull: true
},
}, {
classMethods: {
associate: function (models) {
Post.belongsTo(models.User, {
onDelete: 'CASCADE',
foreignKey: {
fieldName: 'authorId',
allowNull: false
}
});
}
}
});
return Post;
};
I repeted the same data between to file... I come from Laravel so maybe it's usual in the NodeJS world to do things like these.

To avoid code duplication between models and migrations, use models inside migrations as follows:
'use strict';
const { User } = require('../models');
module.exports = {
up: (queryInterface, Sequelize) => {
return User.sync();
// return queryInterface.createTable('user', { id: Sequelize.INTEGER });
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('user');
}
};
EDIT:
This is a good option for adding new tables only!
Caution for "{ alter: true }"! When changing existing columns, be aware that sequelize may not recognise a column rename and will perform two "DROP" and "ADD" operations instead of just one "CHANGE".
So, for updating schema, better use queryInterface.renameColumn and queryInterface.changeColumn or raw queries.
.sync()
queryInterface
raw queries

Yes, currently you have to duplicate the field definitions from Model to its respective Migration, which is really not a great way to build an application.
However there is a package which automatically generates migrations from your Model, you can check it here: https://github.com/flexxnn/sequelize-auto-migrations but is it in early stages.
You can also read discussion on same topic here: https://github.com/sequelize/cli/issues/257

If you want your data not to be null it is a good practice to add DB constraints even though it is not mandatory and you can enforce that in your code.
Regarding your second question, short answer is yes. In your case you are duplicating your code. But it is most likely that you will add or modify your table schema in the future which will cause your model to look different from your first migration file. Remember you don't change a migration file after it ran on production since it runs only once. Any time you want to change your table schema you will have to create a new migration file.

I want also to help future visitors
#Wizix, allowNull is important when you want to avoid empty input in the database. it is validation on the server-side.
Example:
const User = db.define('users', {
fullname: {
type: Sequelize.STRING(50),
allowNull: false,
validate: {
notEmpty: {
args: true,
msg: 'Please provide fullname',
},
},
}
})
I expect user input to not be empty.
allowNull in models validate input before processed to a database
allowNull in migration is the last checking validation where we decide to save to a database or to reject the user input.
duplication of data
I found it necessary to have both conditions validating inputs.

Related

Node.js Sequelize: Error in migration with foreign key

I have an existing SQL Server database where I have to create a new table.
Existing Image table has column as shown in image:
I'm creating a new table using migration with a column referencing to this Image table.
My Query is
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('NewTable', {
IdNewTable: {
primaryKey: true,
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false
},
IdImage: {
type: Sequelize.UUID,
references: {
model: 'Image',
key: 'IdImage',
},
}
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('NewTable');
}
};
While running a migration, I'm getting this error
ERROR: Could not create constraint or index. See previous errors.
Can someone please help me in this?
I was using the wrong datatype
It should be
type: 'UNIQUEIDENTIFIER',
defaultValue: Sequelize.UUIDV4,
primaryKey: true,
allowNull: false,

How do I add a composite foreign key in sequelize?

I have defined the following tables, archive and infos. Each file can have many information tags. The combination of field and tag is unique. For this I require a composite primary key consisting of field and tag. field refers to field in the archive table. The model definitions are given below.
Archive table:
module.exports = (sequelize, DataTypes) => {
let archive = sequelize.define('archive', {
fileid: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false
},
filename: {
type: DataTypes.STRING,
unique: false,
allowNull: false
},
originalname: {
type: DataTypes.STRING,
unique: false,
allowNull: false
},
downloadlink: {
type: DataTypes.STRING,
unique: false,
allowNull: false
},
domain: {
type: DataTypes.STRING,
unique: false,
allowNull: false
},
sem: {
type: DataTypes.INTEGER,
unique: false,
allowNull: false
},
branch: {
type: DataTypes.STRING,
unique: false,
allowNull: false
}
});
archive.associate = models => {
models.archive.hasMany(models.info, {
foreignKey: 'fileid'
});
models.archive.hasMany(models.upvotes, {
foreignKey: 'fileid'
});
};
return archive;
};
Info Table
module.exports = (sequelize, DataTypes) => {
let info = sequelize.define('info', {
tag: {
type: DataTypes.STRING,
allowNull: false,
primaryKey: true
}
});
info.associate = models => {
models.info.belongsTo(models.archive, {
foreignKey: 'fileid',
primaryKey: true
});
};
return info;
};
Making the primaryKey: true did not work. I have tried through: as well. I cannot seem to make it work.
Sequelize can be a giant pain. I follow the docs to the letter and still things don't work with some of the more complex queries, such as can be the case with composite primary key. My suggestion to you is when you bump in to Model fails, go with Raw queries. That being said still take the extra steps to protect against SQL injection attacks.
here is an example:
ensure that you include type
santize variables (in this case code)
db.sequelize
.query('SELECT count(*) FROM logs WHERE code = :code ', {
replacements: {
code: code
},
type: Sequelize.QueryTypes.SELECT
})
.then((data) => {
...
})
.catch((err) => {
...
});
I would have posted this a comment but there was not enough space.

Sequelize – id as uuid – automatic creation with bulkInsert

I have a model with such init:
Group.init({
id: {
type: DataTypes.UUID,
primaryKey: true,
allowNull: false,
defaultValue: DataTypes.UUIDV4,
},
name: Sequelize.STRING,
});
I also have an associated migration file:
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Groups', {
id: {
type: Sequelize.UUID,
primaryKey: true,
allowNull: false,
defaultValue: Sequelize.UUIDV4,
},
name: Sequelize.STRING,
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Groups');
},
};
I'm trying to bulkInsert inside the seeders while creating groups and I'm passing only a name property, expecting the DB to create uuids:
const groups = [{ name: 'group-1' }, { name: 'group-2' }];
return queryInterface.bulkInsert('Groups', groups, {
returning: true,
validate: true,
individualHooks: true,
});
},
Yet there is an error during this seed:
ERROR: null value in column "id" violates not-null constraint
How can I automatically generate uuids?
I faced a similar issue. In my case (on the migration file), I changed
defaultValue: Sequelize.UUIDV4,
to
defaultValue: Sequelize.literal('uuid_generate_v4()'),
PS: I was working on Postgres, so uuid modules needed to be enabled on the schema

Creating associations in Sequelize migration

Nodejs. Sequelize 4.41. Try to make 2 models with relation many-to-many through another table. Running with sequelize-cli, for example...
sequelize model:generate --name Camera --attributes name:string,sn:string
Here is models
// Camera model
'use strict';
module.exports = (sequelize, DataTypes) => {
const Сamera = sequelize.define('Сamera', {
name: DataTypes.STRING,
sn: DataTypes.STRING
}, {});
Сamera.associate = function(models) {
// associations can be defined here
Camera.belongsToMany(models.Relay, {through: 'CameraRelay'});
};
return Сamera;
};
And
// Relay model
'use strict';
module.exports = (sequelize, DataTypes) => {
const Relay = sequelize.define('Relay', {
name: DataTypes.STRING,
sn: DataTypes.STRING,
channel: DataTypes.INTEGER
}, {});
Relay.associate = function(models) {
// associations can be defined here
Relay.belongsToMany(models.Camera, {through: 'CameraRelay'});
};
return Relay;
};
In documentation there are phrase
Belongs-To-Many associations are used to connect sources with multiple targets. Furthermore the targets can also have connections to multiple sources.
Project.belongsToMany(User, {through: 'UserProject'});
User.belongsToMany(Project, {through: 'UserProject'});
This will
create a new model called UserProject with the equivalent foreign keys
projectId and userId.
Migrations is
// create-relay
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Relays', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
sn: {
type: Sequelize.STRING
},
channel: {
type: Sequelize.INTEGER
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Relays');
}
};
And
//create camera
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Сameras', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
sn: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Сameras');
}
};
Why it doesn't create model CameraRelay and doesn't create migration for same model when I running migrate?
I guess the misunderstanding is about sync vs migration: great part of documentation you are referring to, is using the sync method to create all tables and associations starting from models.
When you are using migrations, you are creating db all of your table/columns/associations using migration files (and in my hopinion, this is a better way for something that is going to production).
To understand the difference, just look at your camera model vs your camera migration file:
the model has only name and sn properties defined
the migration file has of course name and sn, but it has id, createdAt and updatedAt too.
Migrations are file with the aim of change your db in a safe way, allowing you to rollback to any point in the past.
So, back to your problem, you have to:
create a new migration file to create your new CameraRelay table, with foreign keys to both Camera and Relay tables
update your current Camera migration file with one-to-many relation to CameraRelay table
update your current Relay migration file with one-to-many relation to CameraRelay table
CameraRelay migration example:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('CameraRelays', {
cameraId: {
primaryKey: true,
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'Relay',
key: 'id'
}
},
relayId: {
primaryKey: true,
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'Camera',
key: 'id'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('CameraRelays');
}
};

Sequelize CLI how to create migrations from models?

I have two models with relations one to many.
I don't understand how to create migration files. Does each model have its own migration file or one migration file can create several tables from models and relations between them (for example as in rails migrations)?
I had a look at many examples including Sequelize docs, and there are primitive examples of models creating and its migration.
//User model
module.exports = function (sequelize, Sequelize) {
var User = sequelize.define('users', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
},
password: {
type: Sequelize.STRING,
allowNull: false,
},
});
return User;
}
//Order model
module.exports = function (sequelize, Sequelize) {
var Order = sequelize.define('orders', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
price: {
type: Sequelize.INTEGER,
allowNull: false,
},
totalPrice: {
type: Sequelize.INTEGER,
allowNull: false,
},
});
return Order;
}
//db.js
//Relations
db.orders.belongsTo(db.users);
db.users.hasMany(db.orders);
Addition
I create migration for two models:
module.exports = {
up: function (queryInterface, Sequelize, done) {
return [
queryInterface.createTable('users', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
},
password: {
type: Sequelize.STRING,
allowNull: false,
},
}),
queryInterface.createTable('orders', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
price: {
type: Sequelize.INTEGER,
allowNull: false,
},
totalPrice: {
type: Sequelize.INTEGER,
allowNull: false,
},
userId: {
type: Sequelize.INTEGER,
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
}
}),
done()
]
},
down: function (queryInterface, Sequelize, done) {
return [
queryInterface.dropTable('users'),
queryInterface.dropTable('orders'),
done()
]
}
};
Do I need to add into my migration file class methods for my models?
//for Order
classMethods: {
associate: function(models) {
Model.belongsTo(models.users, (as: 'users'));
}
}
//for User
classMethods: {
associate: function(models) {
Model.hasMany(models.orders, (as: 'orders'));
}
}
//Addition 2
In order to create new migration file you need to call sequelize migration:create, which creates new file in /migrations directory (that is default migrations directory, can be different). In the migration file you can use bunch of functions in order to create tables, update them or specified table columns etc. If you want you can create all your database tables within single migration file. There is no straight connection between your models and migration files - they are independent on each other. The same concerns relations between models/table. You need to specify that given column in given table references other table.
// example column definition inside migration file
// creates a foreign key referencing table 'users'
userId: {
type: Sequelize.INTEGER,
references: {
model: 'users',
key: 'id'
},
onDelete: 'CASCADE'
}
You just need to remember about consistency between fields definition in Model and field/column definitions in the migration file corresponding to specified model/table.
You can also use command sequelize model:create, which, at the same time, creates a file used for defining a Sequelize model, as well as migration file responsible for creating a table corresponding to this model.
In order to show all possible sequelize-cli commands simply run sequelize help.
EDIT
The class methods like associate must be present only in the Model definition files, not in the migration files.
EDIT 2
The functions used in migration files like createTable are asynchronous, so you cannot simply run them in order just like you did it in your migration file. You can chain them via .then() method or return them as an array like
return [queryInterface.createTable(...), queryInterface.createTable(...)];

Resources