findByIdAndUpdate not failing even when body contains params not in schema - javascript

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:

Related

Node.js + Mongoose: How to use a virtual property to associate an ObjectID with the property?

I'm trying to access a MongoDB database using Node.js and Mongoose.
I created a virtual property in Schema called username. See the code that follows.
const mongoose = require("mongoose");
const User = require("../models/user");
const datatypes = ['temperature', 'humidity'];
const nodeSchema = new mongoose.Schema(
{
MACAddress: {
type: String,
required: true,
trim: true,
uppercase: true,
match: /^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$/,
},
alias: {
type: String,
trim: true,
},
coordinates: {
type: String,
required: false,
match: /^(\-?\d+(\.\d+)?),\s*(\-?\d+(\.\d+)?)$/,
},
address: {
type: String,
required: false,
},
userID: {
type: mongoose.Types.ObjectId,
},
nodeType: {
type: String,
enum: ['router', 'node'],
default: 'node',
},
dataTypes: {
type: [String],
enum: datatypes,
required: true,
}
},
{
timestamps: true,
}
);
The virtual property is used to set the userID property. See the code that follows.
// virtual field
nodeSchema.virtual("username").set(async function (username) {
this.userID = await this.getUserID(username);
});
// methods
nodeSchema.methods = {
getUserID: function (username) {
if (!username) return null;
return User.find({username: username}).then(userDoc => userDoc[0]._id);
},
};
To add a new document to the database, I am using the following code.
const newNode = new Node(newNodeData);
newNode.save().then( (node) => {
console.log(node.userID)
}
)
The problem is this... Calling the User.find function returns a promise. Even using await (see previous code), newNode.save() saves the document in the database without the userID property.
If I change the code to the following snippet, which doesn't use promise, the userID property is saved in the database with no problem. However, this is not what I want.
// virtual field
nodeSchema.virtual("username").set(async function (username) {
let ObjectId = mongoose.Types.ObjectId;
this.userID = new ObjectId("6245e896afe465a25047302e");
});
How can I force newNode.save() to wait for the promise result before saving the document to the database?

How to display Sequelize validation error messages in Express API

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",

Asynchronous Programming in node js to pass constants/predefined mandatory values through mongoose model

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

".findOneAndUpdate()" not updating database properly (Mongodb & Node.js)

I try to use .findOneAndUpdate() to update my database.
No error message, but this part of the database is not updated with new data. The embedded document competitorAnalysisTextData is still empty.
// on routes that end in /users/competitorAnalysisTextData
// ----------------------------------------------------
router.route('/users/competitorAnalysisTextData/:userName')
// update the user info (accessed at PUT http://localhost:8080/api/users/competitorAnalysisTextData)
.post(function(req, res) {
console.log('1');
// Just give instruction to mongodb to find document, change it;
// then finally after mongodb is done, return the result/error as callback.
User.findOneAndUpdate(
{ userName : req.params.userName},
{
$set:
{ "competitorAnalysis.firstObservation" : req.body.firstObservation,
"competitorAnalysis.secondObservation" : req.body.secondObservation,
"competitorAnalysis.thirdObservation" : req.body.thirdObservation,
"competitorAnalysis.brandName" : req.body.brandName,
"competitorAnalysis.productCategory" : req.body.productCategory
}
},
{ upsert: true },
function(err, user) {
// after mongodb is done updating, you are receiving the updated file as callback
console.log('2');
// now you can send the error or updated file to client
if (err)
return res.send(err);
return res.json({ message: 'User updated!' });
});
})
Update
This is my "User" Schema part:
// grab the things we need
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
// Require the crypto module for password hash
'use strict';
var crypto = require('crypto');
// create competitorAnalysisSchema
var CompetitorAnalysis = new Schema({
firstObservation: { type: String },
secondObservation: { type: String },
thirdObservation: { type: String },
brandName: { type: String },
productCategory: { type: String }
});
// create competitorAnalysisPhotoSchema
var CompetitorAnalysisPhoto = new Schema({
photo1: {type: String},
photo2: {type: String},
photo3: {type: String},
photo4: {type: String}
});
// create UserSchema
var UserSchema = new Schema({
userName: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
currentDemo: { type: String },
nextDemo: { type: String },
startTime: { type: String },
startLocation: { type: String },
arriveTime: { type: String },
arriveLocation: { type: String },
leaveTime: { type: String },
leaveLocation: { type: String },
competitorAnalysis: [CompetitorAnalysis],
competitorAnalysisPhoto: [CompetitorAnalysisPhoto],
created_at: Date,
updated_at: Date
});
// the schema is useless so far
// we need to create a model using it
var User = mongoose.model('User', UserSchema);
// make this available to our users in our Node applications
module.exports = User;
in javascript if you wish to update an object inside an array, you need to pick the index
var arr = [{name: "person1"},{name:"person2"}]
arr[0].name = "myname"
arr[1].name = "myFriend"
So it's the same in mongodb, check this link for detail example, or you can manually input the index, for quick hack.
User.findOneAndUpdate(
{ userName : req.params.userName},
{
$set:
{ "competitorAnalysis.0.firstObservation" : req.body.firstObservation,
"competitorAnalysis.0.secondObservation" : req.body.secondObservation,
"competitorAnalysis.0.thirdObservation" : req.body.thirdObservation,
"competitorAnalysis.0.brandName" : req.body.brandName,
"competitorAnalysis.0.productCategory" : req.body.productCategory
}
},
{ upsert: true },
function(err, user) {
// after mongodb is done updating, you are receiving the updated file as callback
console.log('2');
// now you can send the error or updated file to client
if (err)
return res.send(err);
return res.json({ message: 'User updated!' });
});
})
You should use the code above to update nested-array not to add to empty-array.
In javascript, if an array is still empty, we use .push() to add, while in mongodb the command is $push
var arr = []
arr.push({name:"person1"})

MongoDB w/ Mongoose - Where to put syntax to ensureIndex spanning multiple fields?

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

Categories

Resources