Express.js and MySQL model + validation - mysql

I am developing application using Node.js and Express framework. I found many examples of modeling data using MongoDB, but my project requires SQL database.
Could someone make simple explanation, what is the best way to make models based on MySQL?
Also I am wondering how to provide later validation of those models. Maybe I should define validation attributes inside each of them?

There is no best way to make models based on MySQL. You could implement your own way to handle models, but there are many ORM modules available for Node.js, I'd suggest using one of those.
I use Sequelize as ORM to define models and interact with the database in several Express applications. Another ORM for Node that I've run into is Bookshelf.js, but there are many others. Wich one to use depends on your preferences and necessities.
EDIT: Example of usage
I suggest the following structure when using Sequelize models: a directory in your project named models with a file for each model and an index.js file to load the Sequelize environment. If you use the Sequelize CLI, it also has several methods that follow this structure.
index.js
const fs = require("fs");
const path = require("path");
const Sequelize = require("sequelize");
let sqize = new Sequelize({
host : "1.2.3.4",
port : 1234,
database : "testDb",
username : "pino",
password : "th1S1s#c0mpL3xP4sSw0rD",
dialect: 'mysql',
});
fs.readdirSync(__dirname).filter(function(file) {
return (file.indexOf(".") !== 0) && (file !== "index.js");
}).forEach(function(file) {
let model = sequelize.import(path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(function(modelName) {
if ("associate" in db[modelName]) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
db.op = Sequelize.Op;
module.exports = {
sqize: sqize,
Sequelize: Sequelize,
op: Sequelize.Op
};
users.js
module.exports = function (sequelize, DataTypes) {
let users = sequelize.define('users', {
username: {
type: DataTypes.STRING(255),
allowNull: true
},
firstname: {
type: DataTypes.STRING(255),
allowNull: true
},
secondname: {
type: DataTypes.STRING(255),
allowNull: true
},
email: {
type: DataTypes.STRING(255),
allowNull: true
},
type: {
type: DataTypes.INTEGER(4),
allowNull: true,
references: {
model: 'users_type',
key: 'id'
}
},
password: {
type: DataTypes.STRING(255),
allowNull: true
},
salt: {
type: DataTypes.STRING(255),
allowNull: true
}
}, {
tableName: 'users'
});
users.associate = function (models) {
users.belongsTo(models.user_types, {
foreignKey: "type",
as: "userType"
});
users.hasMany(models.user_logs, {
foreignKey: "user_id",
as: "userLogs"
});
};
return users;
};
For more parameters and details, you can check the Sequelize doc, which is very simple and full of examples and details.
Also, I've used some ECMAScript 6, so change or transpile this code if your version of Node.js does not support them.

Related

Sequelize error "Unknown column in 'field list'" during CREATE for One-to-Many Association

I have been using the following tutorial to learn how to implement one-to-many relationship in Sequelize where a Tutorial has many Comment and Comment belongs to a Tutorial: https://www.bezkoder.com/sequelize-associate-one-to-many/
While I have the code modeling what is documented for setting up the relationship between the two models, I receive the following error during creating the Comment model:
Unknown column 'tutorialId' in 'field list'
Additionally, I receive the following SQL output:
Executing (default): INSERT INTO `comments` (`id`,`name`,`text`,`createdAt`,`updatedAt`,`tutorialId`) VALUES (DEFAULT,?,?,?,?,?);
app.js
const tutorialsRouter = require('./routes/api/tutorials');
const commentsRouter = require('./routes/api/comments');
app.use('/api/tutorials', tutorialsRouter);
tutorialsRouter.use('/:tutorialId/comments', commentsRouter);
/models/index.js
db.tutorials = require("./tutorial.model")(sequelize, Sequelize);
db.comments = require("./comment.model")(sequelize, Sequelize);
db.tutorials.hasMany(db.comments, { as: "comments" });
db.comments.belongsTo(db.tutorials, {
foreignKey: "tutorialId",
as: "tutorial",
});
/models/comment.model.js
module.exports = (sequelize, DataTypes) => {
const Comment = sequelize.define('comment', {
name: {
type: DataTypes.STRING,
},
text: {
type: DataTypes.STRING,
},
});
return Comment;
}
/routes/comments.js
const comments = require('../../controllers/comments.controller');
const router = require('express').Router({ mergeParams: true });
router.post('/', comments.create);
module.exports = router;
/controllers/comments.controller.js
const db = require('../models');
const Comment = db.comments;
exports.create = (req, res) => {
...
Comment.create({
name: req.body.name,
text: req.body.text,
tutorialId: req.params.tutorialId,
})
.then( ... )
.catch( ... );
}
Then in Postman I receive a 500 (of the error message above) when issuing the request:
POST localhost:3000/api/tutorials/1/comments
{
"name": "John Doe",
"text": "Lorem ipsum..."
}
I don't think I should have to define a tutorialId field on the Comment model. Grr...
This may be very obvious to some of you, but it's tripping me up trying to learn. Any help is very much appreciated. :)
The issue that you're having is a result of using aliases via the as property. See the docs for belongsTo and hasMany. Here's a code sample that performs the inserts without the error.
let {
Sequelize,
DataTypes,
} = require('sequelize')
async function run () {
let sequelize = new Sequelize('dbname', 'username', 'password', {
host: 'localhost',
port: 5555,
dialect: 'postgres',
logging: console.log
})
let Comment = sequelize.define('comment', {
name: {
type: DataTypes.STRING,
},
text: {
type: DataTypes.STRING,
},
})
let Tutorial = sequelize.define('tutorial', {
title: {
type: DataTypes.STRING,
},
content: {
type: DataTypes.STRING,
}
})
Tutorial.hasMany(Comment)
Comment.belongsTo(Tutorial)
// This just recreates the tables in the database.
// You would really only want to use a force sync
// in a development environment, since it will destroy
// all of the data....
await sequelize.sync({ force: true })
let tutorial = await Tutorial.create({
title: 'Tutorial',
content: 'Hmm....'
})
let comment = await Comment.create({
name: 'Comment',
text: 'Something, something....',
tutorialId: tutorial.id,
})
await sequelize.close()
}
run()
Edit
This is just an edit to my original answer above. The OP Tom Doe discovered that the issue was being caused by a mismatch between the definitions of the tables in the database and the models defined via sequelize (see comments below). As we discovered, one way to troubleshoot the mismatch is to force sync a new version of the database, and then compare the new version of the database with the original version. There may be differences in the definitions of the columns or the contraints. Force syncing the database can be done via the command
await sequelize.sync({ force: true})
Important Note: The above statement will overwrite the existing database and all of its data. See the docs for more information.

Sequelize Error: Include unexpected. Element has to be either a Model, an Association or an object

I am receiving the error when I make a call to my API with a get request:
Include unexpected. Element has to be either a Model, an Association or an object.
My Models look like this:
module.exports = (sequelize, Sequelize) => {
const Productions = sequelize.define("productions", {
id: {
type: Sequelize.SMALLINT,
autoIncrement: true,
primaryKey: true
},
setupTime: {
type: Sequelize.DECIMAL(6, 3)
},
notes: {
type: Sequelize.TEXT
}
}, { timestamps: false });
return Productions;
};
module.exports = (sequelize, Sequelize) => {
const ProductionPrints = sequelize.define("productionPrints", {
id: {
type: Sequelize.SMALLINT,
autoIncrement: true,
primaryKey: true
},
compDate: {
type: Sequelize.DATE
}
}, { timestamps: false });
return ProductionPrints;
};
The relationship between the models is defined here:
db.productions = require("./productions.model.js")(sequelize, Sequelize);
db.productionprints = require("./production-prints.model.js")(sequelize, Sequelize);
db.productions.hasOne(db.productionprints, {
foreignKey: {
name: 'productionId',
allowNull: false
}
});
db.productionprints.belongsTo(db.productions, { foreignKey: 'productionId' });
And the sequelize query looks as so:
const db = require("../models");
const Productions = db.productions;
const ProductionPrints = db.productionPrints;
exports.findAll = (req, res) => {
Productions.findAll({
include: [ { model: ProductionPrints, as: 'prints' } ]
})
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "An error occurred while finding the productions."
});
});
};
I have checked around for others with the issue but have had no avail with any solutions posted on those problems. Generally it was caused by typos, or error in the require paths. I have checked those and all my other includes work, just not on any of the models I include on the productions model.
Any feedback is greatly appreciated.
Error was being caused by a typo:
db.productions = require("./productions.model.js")(sequelize, Sequelize);
db.productionprints = require("./production-prints.model.js")(sequelize, Sequelize);
when this was being referenced in the assigned to a constant:
const Productions = db.productions;
const ProductionPrints = db.productionPrints;
shame on me for changing my case use:
db.productionprints != db.productionPrints
I had the same issue , this is usually caused by naming issue , to track the issue you can check one of the following places to resolve it
check if you are calling the correct model class name
when importing models becarefull not to call the file name instead of model name => the one exported
3.check if you got your association correctly by calling the exported model not the file name
check if your cases e.g users vs Users.
a bonus tip is to use same name for model and file name to avoid these issues because the moment you make them different you likely to make these mistakes
Following the answer of Kelvin Nyadzayo, i have the model.findOne(options) method with a
options.include like this:include: [ { } ] in the options parameter
The include has to have the proper syntax: [{model: Model, as: 'assciationName'}]
And the mine was empty
So this, was triggering the same error

Sequelize in Nodejs creating duplicate tables upon app start

Ok. Landscape: Node, MySql, Sequelize
Issue: After creating a new data model & migration (node migrate.js which creates just fine), upon app start Sequelize creates a duplicate Table (and also forwards form data to the new table).
Ex: db.virtual_class is the main table, and upon start, db.virtual_classes is also created.
My model:
const Sequelize = require('sequelize');
const sequelize = require('../sequelize');
const model = sequelize.define('virtual_class', {
id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true },
style: Sequelize.STRING, // e.g. Style of class
description: Sequelize.STRING(1024), // e.g. class Details
jwt_secret: Sequelize.STRING, // e.g. rando string to be used to gen unique keys for every room
});
module.exports = model;
I've isolated what I think is the issue - I'm including the model in a variable on my index controller for my functions.
const Virtual_class = require('./model');
const classQuery = require('./classQuery');
async function addClass({ style, description, secret }) {
const vClass = await Virtual_class.create({
style,
description,
jwt_secret: secret,
}, { raw: true });
return classQuery(vClass);
}
module.exports = {
addClass,
};
Class Query function to return the data in a usable object:
function classQuery(queryResult) {
if (!queryResult) {
return null;
}
return {
id: queryResult.id,
style: queryResult.style,
description: queryResult.description,
secret: queryResult.jwt_secret,
};
}
module.exports = classQuery;
and the migration:
module.exports = {
up: (sequelize, Sequelize) => sequelize.getQueryInterface().createTable('virtual_class', {
id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
style: {
type: Sequelize.STRING,
},
description: {
type: Sequelize.STRING,
},
jwt_secret: {
type: Sequelize.STRING,
},
createdAt: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.fn('now'),
},
updatedAt: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.fn('now'),
},
}),
down: sequelize => sequelize.getQueryInterface().dropTable('virtual_class'),
};
Net result is fine before I run app - DB shows new table, After running app - DB shows dup table.
I'm a relative noob, and been wracking my brain (and trying to find solutions here) to the problem. I've done this before with other migrations with no issue.
Any advice is appreciated! Thanks!
DOH! For those who are new like me - Sequelize automatically creates plural tables by default, You can force the override tp singular table names.

Sequelize associations not being created in MySQL, PostgreSQL and SQLite

I am defining associations in models using sequalize with MYSQL. But after migration, the foreign key is not being added to the target model as explained in sequelize docs.
I have also tried to manually define foreign keys in models and migration files but still no association is being created between tables. When I view the tables in relation view in PhpMyAdmin, not foreign key constraints or relationship is being created.
I have tried this with SQLite, and PostgreSQL with the same results. I don't know what I am doing wrong. Here are models.
AURHOR MODEL
//One author hasMany books
'use strict';
module.exports = (sequelize, DataTypes) => {
const Author = sequelize.define('Author', {
Name: DataTypes.STRING
}, {});
Author.associate = function(models) {
// associations can be defined here
Author.hasMany(models.Book)
};
return Author;
};
I expect sequelize to add authorId on books table as specified in the docs, but this not happening
BOOK MODEL
//Book belongs to Author
'use strict';
module.exports = (sequelize, DataTypes) => {
const Book = sequelize.define('Book', {
Title: DataTypes.STRING
}, {});
Book.associate = function(models) {
// associations can be defined here
Book.belongsTo(models.Author)
};
return Book;
};
No associations is being created between these two tables after migration.
I have as well tried to define custom foreign keys in model associations like this:
//Author model
Author.hasMany(models.Book,{foreignKey:'AuthorId'})
//Book model
Book.belongsTo(models.Author,{foreignKey:'AuthorId'})
still this not solving the problem
I have gone ahead to define foreign keys in models then referencing them in the association like this:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Book = sequelize.define('Book', {
Title: DataTypes.STRING,
AuthorId:DataTypes.INTEGER
}, {});
Book.associate = function(models) {
// associations can be defined here
Book.belongsTo(models.Author,{foreignKey:'AuthorId'})
};
return Book;
};
But still no associations is being created
I finally decided to add references in migration files like so:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Books', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
Title: {
type: Sequelize.STRING
},
AuthorId:{
type: Sequelize.INTEGER,
references:{
model:'Author',
key:'id'
}
}
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Books');
}
};
But when I run this kind of migration setup, I get this error: ERROR: Can't create table dbname.books (errno: 150 "Foreign key constraint is i
ncorrectly formed")
I get similar error when I switch to PostgreSQL.
I have been held back by this issue for quite long. What may I doing wrong. I am using sequelize version 4.31.2 with sequelize CLI.
I was referencing to models wrongly in migrations.
Wrong way
AuthorId:{
type: Sequelize.INTEGER,
references:{
model:'Author',
key:'id'
}
}
Correct Way
// Notes the model value is in lower case and plural just like the table name in the database
AuthorId:{
type: Sequelize.INTEGER,
references:{
**model:'authors',**
key:'id'
}
}
This solved my problem. The associations is now getting defined.

How to update migration files when change column or add new column using Sequelize

I'm new in Sequelize, right now I'm creating a RESTful api with NodeJS and Sequelize. I'm trying to figure out how to change my Database Schema like change my Column name using Sequelize
I create a Model like this
sequelize model:create --name MyUser --attributes first_name:string,last_name:string,bio:text
It created a file in Models
'use strict';
module.exports = function(sequelize, DataTypes) {
var Page = sequelize.define('Page', {
name: DataTypes.STRING,
text: DataTypes.TEXT,
url: DataTypes.STRING
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
}
}
});
return Page;
};
and one file in Migrations Folder
'use strict';
module.exports = {
up: function(queryInterface, Sequelize) {
return queryInterface.createTable('Pages', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
text: {
type: Sequelize.TEXT
},
url: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: function(queryInterface, Sequelize) {
return queryInterface.dropTable('Pages');
}
};
The problem is how about if I want to add New column and change existing Column Name
Example I want to change to this
'use strict';
module.exports = function(sequelize, DataTypes) {
var Page = sequelize.define('Page', {
fullname: DataTypes.STRING,
text: DataTypes.TEXT,
url: DataTypes.STRING,
email: DataTypes.STRING
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
}
}
});
return Page;
};
I have read a few page in Stackoverflow about this, like in this page
How to auto generate migrations with Sequelize CLI from Sequelize models?
and
Sequelize.js: how to use migrations and sync
One of that page has a way to Alter Column automatic using Sequelize-cmd in this link https://www.youtube.com/watch?v=wHTBxtk8ezo but Sequelize-cmd is already deprecated and the other way and the only way I do now is create Migration File using sequelize migration:createand manually write a code to rename and add Column using addColumn and renameColumn
So, my question now is there a way to Creating Migration File with addColumn and renameColumn Automatic like what Sequelize-cmd do without have to write it manually ?
First type sequelize migration:create --name changeColumn into your terminal after navigating to your project directory. This creates a new migration file in your migrations folder called changeColumn with the date of creation prepended.
Then you use the renameColumn method which takes in the table name, original column name and the new column name. Once you have updated the file to the below code, go back to your terminal and type sequelize db:migrate to run the migration.
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.renameColumn('Users',
'beforeName', 'afterName');
},
down: (queryInterface, Sequelize) => {
return queryInterface.renameColumn('Users', 'afterName', 'beforeName');
}
};
Simplest way to do this is
queryInterface.renameColumn('tableName', 'oldName', 'newName');
NOTE: I tries this in down function but not work it's working once I write this code in up function
There is a new npm package for solving this problem by comparing your models files and writing migration files for you
Sequelize-mig
Install it with:
npm install sequelize-mig -g / yarn global add sequelize-mig
then use it like this
sequelize-mig migration:make -n <migration name>
and it will auto generate the migration file for you with all updates read from your models files