I have this Organization model used in a Node.js/Express API with Sequelize ORM running MySQL. When I violate the 2-100 character rule under validation in the first code example below I get the classic err item from the catch block in the second code example, which doesn't contain any information about the validation error.
I would like instead to display the validation error message you can see under validation { len: { msg: ...}} in the model. At least console.log it, and then later display it to the end user.
However, the Sequelize manual and any other information I can find don't explain how I make use of this custom error message. So my question is how can I make use of it and display it.
Model:
'use strict'
const { Sequelize, DataTypes } = require('sequelize');
const db = require('./../config/db.js')
const Organization = db.define('organizations', {
id: {
type: DataTypes.UUID,
defaultValue: Sequelize.UUIDV4,
primaryKey: true,
allowNull: false,
unique: true,
validate: {
isUUID: {
args: 4,
msg: 'The ID must be a UUID4 string'
}
}
},
name: {
type: DataTypes.STRING,
required: true,
allowNull: false,
validate: {
len: {
args: [2, 100],
msg: 'The name must contain between 2 and 100 characters.' // Error message I want to display
}
}
},
created_at: {
type: DataTypes.DATE,
required: true,
allowNull: false
},
updated_at: {
type: DataTypes.DATE,
required: true,
allowNull: false
},
deleted_at: {
type: DataTypes.DATE
}
},
{
underscored: true,
paranoid: true,
tableName: 'organizations',
updatedAt: 'updated_at',
createdAt: 'created_at',
deletedAt: 'deleted_at'
})
module.exports = Organization
Controller:
/**
* #description Create new organization
* #route POST /api/v1/organizations
*/
exports.createOrganization = async (req, res, next) => {
try {
const org = await Organization.create(
req.body,
{
fields: ['name', 'type']
})
return res.status(200).json({
success: true,
data: {
id: org.id,
name: org.name
},
msg: `${org.name} has been successfully created.`
})
} catch (err) {
next(new ErrorResponse(`Sorry, could not save the new organization`, 404))
// ^ This is the message I get if I violate the validation rule ^
}
}
The Sequelize documentation for validation and constraints is found here: https://sequelize.org/master/manual/validations-and-constraints.html
The validation is built on Validatorjs (https://github.com/validatorjs/validator.js) which unfortunately also lacks practical info on the use of the validation object. I guess that means it must be self explanatory, but as I'm a noob I am lost.
I tried your same validation on my local project on firstName field and I could get sequelize error like this
console.log('err.name', err.name);
console.log('err.message', err.message);
console.log('err.errors', err.errors);
err.errors.map(e => console.log(e.message)) // The name must contain between 2 and 100 characters.
as you can see you can check if err.name is SequelizeValidationError and then loop over err.errors array and get message for field on path and rest other properties are also there.
Error Display example:
const errObj = {};
err.errors.map( er => {
errObj[er.path] = er.message;
})
console.log(errObj);
you'll get an object like
{
firstName: 'The firstName must contain between 2 and 100 characters.',
lastName: 'The lastName must contain between 2 and 100 characters.'
}
After getting help from #Rohit Ambre my catch block is like this:
} catch (err) {
if (err.name === 'SequelizeValidationError') {
return res.status(400).json({
success: false,
msg: err.errors.map(e => e.message)
})
} else {
next(new ErrorResponse(`Sorry, could not save ${req.body.name}`, 404))
}
}
API response:
{
"success": false,
"msg": [
"The name must contain between 2 and 100 characters.",
"The organization type is not valid."
]
}
It would maybe helpful to add a key for each item, something like this, but whatever I do in the catch block it seems to give me an UnhandledPromiseRejectionWarning:
catch (err) {
if (err.name === 'SequelizeValidationError') {
const errors = err.errors
const errorList = errors.map(e => {
let obj = {}
obj[e] = e.message
return obj;
})
return res.status(400).json({
success: false,
msg: errorList
})
} else {
next(new ErrorResponse(`Sorry, could not save ${req.body.name}`, 404))
}
}
Any tips, por favor?
you can execute a simple flag --debug in the migration
npx sequelize-cli db:migrate --debug
backend
response.err(res, err.errors[0].message ?? 'tidak berhasil menambahkan data', 500);
frontend
.catch(error => {
....
this.fetchLoading = false
this.$swal({
title: 'Error!',
text: `${error.response.data.message ?? 'except your message'}`,
"sequelize": "^6.6.5",
"sequelize-cli": "^6.2.0",
Related
Thank you in advance for any help that anyone can provide.
I currently have something like below for my mongoose schema:
const personSchema = new mongoose.Schema({
name: {
type: String,
required: true,
minlength: 3,
unique: true
},
number: {
type: String,
validate: {
// below regex matches 8 digits together regardless of any other characters in between that are not digits
validator: function(v) { return /([^\d]*\d){8,}/.test(v) }
},
required: true
},
})
// applying the uniqueness validator
personSchema.plugin(uniqueValidator)
personSchema.set('toJSON', {
transform: (document, returnedObject) => {
returnedObject.id = returnedObject._id.toString()
delete returnedObject._id
delete returnedObject.__v
}
})
module.exports = mongoose.model('Person', personSchema)
And in my index.js for my function for handling update calls, it looks like this:
app.put('/api/persons/:id', (request, response, next) => {
const body = request.body
const person = {
name: body.name,
number: body.number,
}
Person.findByIdAndUpdate(request.params.id, person, {new: true})
.then(updatedPerson => {
response.json(updatedPerson)
})
.catch(error => next(error))
})
So initially, this worked perfectly fine, but after I add the runValidators: true option which looks like this now:
Person.findByIdAndUpdate(request.params.id, person, {new: true, runValidators: true,})
I get this new error that states:
"error": "Validation failed: name: Cannot read property 'ownerDocument' of null"
I'm not sure what ownerDocument even is, and I'm also unclear on why making the update function run the validator is causing this as well. Any help would be appreciated!
If you're using the mongoose-unique-validator plugin with findOneAndUpdate and related methods, you need to set the context option to 'query' to set the value of this to the query object in validators.
Person.findByIdAndUpdate(request.params.id, person, {
new: true,
runValidators: true,
context: 'query',
})
https://github.com/blakehaswell/mongoose-unique-validator#find--updates
Taken from This MERN tutorial...
I have a mongoose schema with 4 fields:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
let Todo = new Schema({
name: {
type: String,
required: true
},
description: {
type: String,
required: false
},
comments: {
type: String,
required: false
},
done: {
type: Boolean,
required: true
},
});
module.exports = mongoose.model('Todo', Todo);
I'm calling this update route:
todoRoutes.route('/update/:id').post(function(req, res) {
Todo.findByIdAndUpdate(req.params.id, req.body, function(err, todo) {
if (err)
res.status(400).send('Updating item failed: ' + err);
else
todo.save().then(todo => {
res.json('Item updated!');
}).catch(err => {
res.status(400).send("Update not possible: " + err);
});
});
});
with the following body:
{
"name": "bla"
}
and gets an "ok" status, and the document is updated as I wanted. However, running the same update with an extra field, also gets an "ok" status, though it should have failed IMO:
{
"name": "bla",
"unwanted_field": true
}
The field is not shown when I GET the DB, but it still returns without any error. Why?
Why update does not require the "required" fields, and accepts any updates?
enter link description here has a strict option, that can be used like this:
let Todo = new Schema({
name: {
type: String,
required: true
},
description: {
type: String,
required: false
},
comments: {
type: String,
required: false
},
done: {
type: Boolean,
required: true
},
},
{
strict: true // This is true by default. Changing this to "throw" will throw an error, as shown in the image attached.
}
);
I add here an image of an error I got using postman, trying to add bad data after adding the strict: "throw" option:
I'm attempting to add bycrypt to an authentication table but I'm getting an error that authTable is undefined when attempting to add the generateHash and validPassword methods. Is there something i'm not understanding about the authTables.methods portion?
The code works when I comment out the generateHash and validPassword portion which leads me to believe that .methods is the hang up.
var bcrypt = require("bcryptjs");
module.exports = function (sequelize, DataTypes) {
var authTable = sequelize.define("authTable", {
username: {
type: DataTypes.STRING,
allowNull: false,
validate: {
len: [1, 30]
}
},
password: {
type: DataTypes.STRING,
allowNull: false,
validate: {
len: [6, 20]
}
},
email: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isEmail: true,
len: [1]
}
}
});
authTable.methods.generateHash = function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(10), null);
};
authTable.methods.validPassword = function(password) {
return bcrypt.compareSync(password, this.local.password);
};
return authTable;
}
I would expect this to go to the table within the database with an encrypted password.
The errors i'm receiving are:
TypeError: Cannot set property 'generateHash' of undefined.
TypeError: Cannot set property 'validPassword' of undefined
I'm getting an error that authTable is undefined
No, you don't. You get an error that authTable.methods is undefined.
define returns an instance of Model, and as you can see in the documentation of Model, it does not have a property named methods, ergo, authTable.methods will evaluate to undefined.
I have multiple questions, please go through my code.
1) how to pass constants/predefined mandatory values through model?
For eg. I have some fields which user must be passing the values and some constants to pass on inside the kafkaSchema.config[ ] and also livySchema.args[ ]. The code i want to pass through is in second question on the same question thread.
const mongoose = require('mongoose');
const livy_schema = mongoose.Schema({
file: { type: String, required: true },
name: { type: String, required: true },
className: { type: String, required: true },
args: [{ type: mongoose.Schema.Types.Mixed, required: true }] //here i have constants to pass on to
});
const kafka_schema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true, unique: false },
config: { type: mongoose.Schema.Types.Mixed, required: true } //here i have constants to pass on to
});
const enrichedEventSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
projectId: { type: mongoose.Schema.Types.ObjectId, ref: 'Project', required: true },
name: { type: String, required: true, unique: true },
description: { type: String, required: false },
type: { type: String, enum: ["Enriched"], required: true },
format: { type: String, enum: ["JSON", "DELIMITED", "FixedWidth", "LOG"], required: true },
kafka: [kafka_schema],
livy: [livy_schema] // how to make this schema required:true?
});
module.exports = mongoose.model('EnrichedEvent', enrichedEventSchema);
2) how to make this code to run asynchronously, Right now its working synchronously. For example, Its able to save the eventdata in event collection in database, then its updating the project collection, then calling axios.post method to call my livy server and kafka server in order. What i want to do is save the eventdata in event collection in database, then update the project collection (synchronously), meanwhile I want to call my livy and kafka server at the same time (Asynchronously).
router.post("/:projectId/events/enriched", (req, res, next) => {
const enrichedEvent = new EnrichedEvent({
_id: mongoose.Types.ObjectId(),
name: req.body.name,
projectId: req.params.projectId, //taking from url
description: req.body.description,
type: req.body.type,
format: req.body.format,
kafka: req.body.kafka,
livy: req.body.livy
});
enrichedEvent.save()
.then(result => {
console.log(result);
res.status(201).json({
message: "Event stored",
createdEvent: {
_id: result._id,
projectId: result.projectId,
name: result.name,
description: result.description,
type: result.type,
kafka: result.kafka,
livy: result.livy
}
});
Project.findOneAndUpdate({ _id: result.projectId },
{ $push: { enrichedEvents: result._id } })
axios.post("http://52.xxx.xxx.199:8998/batches", result.livy)
.then(function (response) {
console.log(response);
})
.then(axios.get("http://52.xxx.xxx.199:8998/batches/"), function (res) {
console.log(res);
})
axios.post("http://52.xxx.xxx.199:8083/connectors", result.kafka)
.then(function (response) {
console.log(response);
})
.catch(err => {
console.log(err);
res.status(500).json({
error: err
});
});
});
});
Question may seem bit lengthy, but valid question to ask on SO. Please guide me to right direction.
1)
const enrichedEventSchema = mongoose.Schema({
// ...
livy: { type: [livy_schema], required: true }
});
2)
return enrichedEvent.save().
then(result => {
// ...
return Project.findOneAndUpdate(/*...*/);
}).
then(() => {
// ...
return Promise.all([axios.post(/*...*/), axios.post(/*...*/]);
});
Hey try the following:
1) for saving user's entered configurations and also having default constants. You could use mongoose pre save hook.
https://mongoosejs.com/docs/middleware.html#pre
livy_schema.pre('save', function(next) {
this.args = { ...this.args, ...CONSTANTS }; //I'm use es6's spread operator
next();
});
kafka_schema.pre('save', function(next) {
this.config = { ...this.config, ...CONSTANTS }; //I'm use es6's spread operator
next();
});
2) For second question: try following:
axios.all([
axios.post("http://52.221.178.199:8998/batches", result.livy),
axios.post("http://52.221.178.199:8083/connectors", result.kafka)
]);
I'm trying to implement this solution and I'm not sure where to put it. I see the db variable called frequently, but I'm still new to node and mongoDb, so I don't know how to call it in my Model. Here is the syntax to ensure an index spanning multiple fields...
db.collection.ensureIndex( {
description: "text",
title: "text"
} );
Here is my model...
// Module dependencies.
var mongoose = require('mongoose'),
config = require('../../config/config'),
Schema = mongoose.Schema,
findOrCreate = require('mongoose-findorcreate'),
textSearch = require('mongoose-text-search');
// Product Schema
var ProductSchema = new Schema({
created: {
type: Date,
default: Date.now
},
retailer: {
type: String,
required: true,
trim: true
},
retailer_category: {
type: String,
required: true,
trim: true
},
product_id: {
type: String,
required: true,
trim: true
},
link: {
type: String,
trim: true
},
title: {
type: String,
trim: true
},
price: {
type: Number
},
// Rating - 0 out of 5 (can be decimal)
rating: {
type: Number
},
description: {
type: String,
trim: true
},
variations: {
type: Schema.Types.Mixed,
default: []
},
images: {
type: Boolean,
default: false
}
});
// Validations
ProductSchema.index({ retailer: 1, product_id: 1 }, { unique: true });
// Statics
ProductSchema.statics = {
load: function(id, cb) {
this.findOne({
_id: id
}).exec(cb);
}
};
// Plug-Ins
ProductSchema.plugin(findOrCreate);
ProductSchema.plugin(textSearch);
mongoose.model('Product', ProductSchema);
var Product = mongoose.model('Product', ProductSchema);
Product.ensureIndexes( function(err) {
if (err) {
console.log(err);
}
})
It's worth noting:
When your application starts up, Mongoose automatically calls ensureIndex for each defined index in your schema. While nice for development, it is recommended this behavior be disabled in production since index creation can cause a significant performance impact. Disable the behavior by setting the autoIndex option of your schema to false.
from http://mongoosejs.com/docs/guide.html
I scratched my head over this one too. After digging around the mongoose test cases, I found that ensureIndex resides in a mongoose model's collection property.
var ProductModel = mongoose.model('Product', ProductSchema);
ProductModel.collection.ensureIndex({
description : 'text',
title : 'text'
}, function(error, res) {
if(error){
return console.error('failed ensureIndex with error', error);
}
console.log('ensureIndex succeeded with response', res);
});
Note that a callback is required, or Mongo will throw the error:
Error: Cannot use a writeConcern without a provided callback