API to insert data to array of objects in mongoDB - node.js

I am trying to insert array of objects inside array of objects in my mongoDB schema. This is how i want my schema to appear.
const CourseSchema = mongoose.Schema({
categoryname: {
type: String,
required: "Course Category",
min: 3,
max: 100
},
coursename: {
type: String,
required: "Course Name",
min: 3,
max: 100
},
levels:
[
{
levelid: Number,
levelname: String,
chapter:
[
{
chapternumber: Number,
chaptername: String,
content: String //To be elaborated
}
]
}
]
});
My API which i have written looks like this:
exports.addcourse = (req, res) => {
let levels = [];
levels.push({
levelid: req.body.levelid,
levelname: req.body.levelname,
chapter: [
{
chapternumber: req.body.chapternumber,
chaptername: req.body.chaptername,
content: req.body.content
}
]
})
const newCourse = new Course({
coursename: req.body.coursename,
categoryname: req.body.categoryname,
levels: levels
});
newCourse.save(function (error) {
if (error) res.json({ message: 'could not add course because ' + error });
res.json({ newCourse: newCourse });
});
}
This works fine when i enter one level and one chapter, but gives an error when i enter multiple data.
I am giving input from postman 'x-www'form-urlencoded'.
please help.
The error i get when i add one more levelid and levelname in postman
{
"message": "could not add course because ValidationError: levels.0.levelid: Cast to Number failed for value \"[ '1', '2' ]\" at path \"levelid\", levels.0.levelname: Cast to String failed for value \"[ 'First Level', 'Second Level' ]\" at path \"levelname\""
}
The data i am trying to enter

In postman, when you are sending the same key levelid twice, it converts it to array containing both the values. Like in your case, in req.body.levelid, you will receive [1, 2]. But in your schema, levelid is expecting a number value. Also, you are reading it wrong in your API code. Instead of getting levelid and chapter seperately, you can get the levels array in request body with values levelid, levelname and chapter. Similarly, chapter can be an array of objects. req.body.levels will look like this:
[{
levelid: 1,
levelname: "level1",
chapter: [{
chapternumber: 1,
chaptername: "chapter1",
content: "chapter1-content"
}, {
chapternumber: 2,
chaptername: "chapter2",
content: "chapter2-content"
}]
}]
Try this in postman by giving input in raw

Related

Parsing posted object array, Express

I need to parse data with Express from form:
invoiceRouter.post('/', async (req,res) => {
console.log(req.body);
let invoice = new Invoice();
invoice = req.body;
invoice.status = 0;
//save
res.redirect('/invoices');
});
When I log, the array of objects is read as list of values:
{
createdDate: '2021-10-15',
invoiceRows: [ 'Title3', '2', 'Title2', '3' ]
}
But it can not read the invoiceRows as array of 2, therefore I am struggling to parse it into array for saving it.
When I set the extended: false, I can see following result from req.body:
[Object: null prototype] {
createdDate: '2021-10-15',
'invoiceRows[0].productTitle': 'Title2',
'invoiceRows[0].unitPrice': '2',
'invoiceRows[1].productTitle': 'Title3',
'invoiceRows[1].unitPrice': '2'
}
The schema I am using:
const invoiceSchema = new mongoose.Schema({
createdDate: {
type: Date,
required: true
},
status: {
type: Number,
required: true
},
invoiceRows: [{
productTitle: String,
unitPrice: Number
}]
});
Question: what am I doing wrong, in order to get array of objects from req.body inside parent object?
In your req.body you should be receiving like bellow (As per your model schema). Make your front end to send data like bellow.
{
createdDate: '2021-10-15',
invoiceRows: [ { productTitle :'Title1', unitPrice : 2}, { productTitle :'Title2', unitPrice : 3} ]
}

implement a search functionality that query MongoDB database and return all the objects which contain a user given value in node js and express

I want to implement a search functionality that would query my MongoDB database and return all the objects which contain (full/partially) the name I am searching for.
Example:
My object collection is products, and I want to see every product which contains the name I search, from the product names.
My 'Products' collection looks like this...
[ { _id: 5f79,
productName: 'Test-image12345',
price: 60,
details: 'Test product' },
{ _id: 5f7d,
productName: 'Test-image1234',
price: 60,
details: 'Test product'},
{ _id: 5fv4,
productName: 'Test',
price: 60,
details: 'Test product'},
]
Now I need to find all the products with "Test-image1234"
// search a product by name
productRoute.get('/getproduct/:name', async (req,res) => {
try {
const findname = req.params.name;
const objs = await Product.find({productName:{ $regex:'.*'+findname+'.*'} });
res.json(objs);
} catch (error) {
res.json({message: error});
}
})
Now I get the answer as follows...
[ { _id: 5f79,
productName: 'Test-image12345',
price: 60,
details: 'Test product' },
{ _id: 5f7d,
productName: 'Test-image1234',
price: 60,
details: 'Test product'}
]

Mongoose error findByIdAndUpdate fails in cast

Trying to update a document using findByIdAndUpdate, i get an error that i don't understand.
console.log(req.body);
var data = req.body;
data._id = undefined;
Package.findByIdAndUpdate(req.params.id, data, function (err, pkg) {
if (err) {
console.log(err.stack);
return next(restify.InternalServerError(err));
}
res.json(pkg);
next();
});
I get the following error:
TypeError: Cannot read property '_id' of undefined
at ObjectId.cast (/home/ubuntu/workspace/server/node_modules/mongoose/lib/schema/objectid.js:109:12)
at ObjectId.castForQuery (/home/ubuntu/workspace/server/node_modules/mongoose/lib/schema/objectid.js:165:17)
at Query._castUpdateVal (/home/ubuntu/workspace/server/node_modules/mongoose/lib/query.js:2009:17)
at Query._walkUpdatePath (/home/ubuntu/workspace/server/node_modules/mongoose/lib/query.js:1969:25)
at Query._castUpdate (/home/ubuntu/workspace/server/node_modules/mongoose/lib/query.js:1865:23)
at castDoc (/home/ubuntu/workspace/server/node_modules/mongoose/lib/query.js:2032:18)
at Query._findAndModify (/home/ubuntu/workspace/server/node_modules/mongoose/lib/query.js:1509:17)
at Query.findOneAndUpdate (/home/ubuntu/workspace/server/node_modules/mongoose/node_modules/mquery/lib/mquery.js:2056:15)
at Function.Model.findOneAndUpdate (/home/ubuntu/workspace/server/node_modules/mongoose/lib/model.js:1250:13)
at Function.Model.findByIdAndUpdate (/home/ubuntu/workspace/server/node_modules/mongoose/lib/model.js:1344:32)
I have verified that the id is valid, data is a valid object as well.
My model:
mongoose.model('Package', {
name: {
required: true,
type: String
},
servers: [mongoose.Schema.Types.ObjectId],
packageType: {
type: String,
enum: ['package', 'subscription']
},
subscriptionPeriodInDays: Number,
pointsIncluded: Number,
price: Number,
rank: String,
data: mongoose.Schema.Types.Mixed //For custom solutions
});
The log also prints a valid data object
{
name: 'Your Package',
packageType: 'subscription',
subscriptionPeriodInDays: 30,
pointsIncluded: 10000,
price: 10,
rank: 'Donator',
_id: undefined,
__v: 0,
servers: [],
description: '<p>test</p>\n'
}
I have tried to step trough with the debugger but i couldn't find a reason for this.
As Raleigh said, you need to remove _id field. You can do it by delete data._id; instead of data._id = undefined;.
I believe Mongoose is trying to set the value of _id to undefined since the _id value is still getting passed in via the data object.
Try removing the line data._id = undefined; before you update the model or completely remove the _id field from the data object.

How to define object in array in Mongoose schema correctly with 2d geo index

I'm currently having problems in creating a schema for the document below. The response from the server always returns the "trk" field values as [Object]. Somehow I have no idea how this should work, as I tried at least all approaches which made sense to me ;-)
If this helps, my Mongoose version is 3.6.20 and MongoDB 2.4.7
And before I forget, it would be nice to also set it as Index (2d)
Original data:
{
"_id": ObjectId("51ec4ac3eb7f7c701b000000"),
"gpx": {
"metadata": {
"desc": "Nürburgring VLN-Variante",
"country": "de",
"isActive": true
},
"trk": [
{
"lat": 50.3299594,
"lng": 6.9393006
},
{
"lat": 50.3295046,
"lng": 6.9390688
},
{
"lat": 50.3293714,
"lng": 6.9389939
},
{
"lat": 50.3293284,
"lng": 6.9389634
}]
}
}
Mongoose Schema:
var TrackSchema = Schema({
_id: Schema.ObjectId,
gpx: {
metadata: {
desc: String,
country: String,
isActive: Boolean
},
trk: [{lat:Number, lng:Number}]
}
}, { collection: "tracks" });
The response from the Network tab in Chrome always looks like this (that's only the trk-part which is wrong) :
{ trk:
[ [Object],
[Object],
[Object],
[Object],
[Object],
[Object],
I already tried different Schema definitions for "trk":
trk: Schema.Types.Mixed
trk: [Schema.Types.Mixed]
trk:[ { type:[Number], index: "2d" }]
Hope you can help me ;-)
You can declare trk by the following ways : -
either
trk : [{
lat : String,
lng : String
}]
or
trk : { type : Array , "default" : [] }
In the second case during insertion make the object and push it into the array like
db.update({'Searching criteria goes here'},
{
$push : {
trk : {
"lat": 50.3293714,
"lng": 6.9389939
} //inserted data is the object to be inserted
}
});
or you can set the Array of object by
db.update ({'seraching criteria goes here ' },
{
$set : {
trk : [ {
"lat": 50.3293714,
"lng": 6.9389939
},
{
"lat": 50.3293284,
"lng": 6.9389634
}
]//'inserted Array containing the list of object'
}
});
I had a similar issue with mongoose :
fields:
[ '[object Object]',
'[object Object]',
'[object Object]',
'[object Object]' ] }
In fact, I was using "type" as a property name in my schema :
fields: [
{
name: String,
type: {
type: String
},
registrationEnabled: Boolean,
checkinEnabled: Boolean
}
]
To avoid that behavior, you have to change the parameter to :
fields: [
{
name: String,
type: {
type: { type: String }
},
registrationEnabled: Boolean,
checkinEnabled: Boolean
}
]
For making an array in schema we have to make one more schema as monetizationSchema which is used to store one data at a time and another as blogSchema we have monetization field containing monetizationSchema in square brackets as an array.
Schema for storing one data at a time.
const monetizationSchema = new Schema({
amazonUrl: {
type: String,
required: true,
}
});
Schema with monetization as an array.
const blogSchema = {
monetization: [
monetizationSchema
],
image: {
type: String,
required: true
},
// ... etc
});
You can declare an array as below
trk : [{
lat : String,
lng : String
}]
but it will set [] (empty array) as the default value.
If you don't want this default value then to overwrite this default, you need to set the default value to undefined as below
trk: {
type: [{
lat : String,
lng : String
}],
default: undefined
}
The problem I need to solve is to store contracts containing a few fields (address, book, num_of_days, borrower_addr, blk_data), blk_data is a transaction list (block number and transaction address).
This question and answer helped me. I would like to share my code as below. Hope this helps.
Schema definition. See blk_data.
var ContractSchema = new Schema(
{
address: {type: String, required: true, max: 100}, //contract address
// book_id: {type: String, required: true, max: 100}, //book id in the book collection
book: { type: Schema.ObjectId, ref: 'clc_books', required: true }, // Reference to the associated book.
num_of_days: {type: Number, required: true, min: 1},
borrower_addr: {type: String, required: true, max: 100},
// status: {type: String, enum: ['available', 'Created', 'Locked', 'Inactive'], default:'Created'},
blk_data: [{
tx_addr: {type: String, max: 100}, // to do: change to a list
block_number: {type: String, max: 100}, // to do: change to a list
}]
}
);
Create a record for the collection in the MongoDB. See blk_data.
// Post submit a smart contract proposal to borrowing a specific book.
exports.ctr_contract_propose_post = [
// Validate fields
body('book_id', 'book_id must not be empty.').isLength({ min: 1 }).trim(),
body('req_addr', 'req_addr must not be empty.').isLength({ min: 1 }).trim(),
body('new_contract_addr', 'contract_addr must not be empty.').isLength({ min: 1 }).trim(),
body('tx_addr', 'tx_addr must not be empty.').isLength({ min: 1 }).trim(),
body('block_number', 'block_number must not be empty.').isLength({ min: 1 }).trim(),
body('num_of_days', 'num_of_days must not be empty.').isLength({ min: 1 }).trim(),
// Sanitize fields.
sanitizeBody('*').escape(),
// Process request after validation and sanitization.
(req, res, next) => {
// Extract the validation errors from a request.
const errors = validationResult(req);
if (!errors.isEmpty()) {
// There are errors. Render form again with sanitized values/error messages.
res.status(400).send({ errors: errors.array() });
return;
}
// Create a Book object with escaped/trimmed data and old id.
var book_fields =
{
_id: req.body.book_id, // This is required, or a new ID will be assigned!
cur_contract: req.body.new_contract_addr,
status: 'await_approval'
};
async.parallel({
//call the function get book model
books: function(callback) {
Book.findByIdAndUpdate(req.body.book_id, book_fields, {}).exec(callback);
},
}, function(error, results) {
if (error) {
res.status(400).send({ errors: errors.array() });
return;
}
if (results.books.isNew) {
// res.render('pg_error', {
// title: 'Proposing a smart contract to borrow the book',
// c: errors.array()
// });
res.status(400).send({ errors: errors.array() });
return;
}
var contract = new Contract(
{
address: req.body.new_contract_addr,
book: req.body.book_id,
num_of_days: req.body.num_of_days,
borrower_addr: req.body.req_addr
});
var blk_data = {
tx_addr: req.body.tx_addr,
block_number: req.body.block_number
};
contract.blk_data.push(blk_data);
// Data from form is valid. Save book.
contract.save(function (err) {
if (err) { return next(err); }
// Successful - redirect to new book record.
resObj = {
"res": contract.url
};
res.status(200).send(JSON.stringify(resObj));
// res.redirect();
});
});
},
];
Update a record. See blk_data.
// Post lender accept borrow proposal.
exports.ctr_contract_propose_accept_post = [
// Validate fields
body('book_id', 'book_id must not be empty.').isLength({ min: 1 }).trim(),
body('contract_id', 'book_id must not be empty.').isLength({ min: 1 }).trim(),
body('tx_addr', 'tx_addr must not be empty.').isLength({ min: 1 }).trim(),
body('block_number', 'block_number must not be empty.').isLength({ min: 1 }).trim(),
// Sanitize fields.
sanitizeBody('*').escape(),
// Process request after validation and sanitization.
(req, res, next) => {
// Extract the validation errors from a request.
const errors = validationResult(req);
if (!errors.isEmpty()) {
// There are errors. Render form again with sanitized values/error messages.
res.status(400).send({ errors: errors.array() });
return;
}
// Create a Book object with escaped/trimmed data
var book_fields =
{
_id: req.body.book_id, // This is required, or a new ID will be assigned!
status: 'on_loan'
};
// Create a contract object with escaped/trimmed data
var contract_fields = {
$push: {
blk_data: {
tx_addr: req.body.tx_addr,
block_number: req.body.block_number
}
}
};
async.parallel({
//call the function get book model
book: function(callback) {
Book.findByIdAndUpdate(req.body.book_id, book_fields, {}).exec(callback);
},
contract: function(callback) {
Contract.findByIdAndUpdate(req.body.contract_id, contract_fields, {}).exec(callback);
},
}, function(error, results) {
if (error) {
res.status(400).send({ errors: errors.array() });
return;
}
if ((results.book.isNew) || (results.contract.isNew)) {
res.status(400).send({ errors: errors.array() });
return;
}
var resObj = {
"res": results.contract.url
};
res.status(200).send(JSON.stringify(resObj));
});
},
];
Thanks for the replies.
I tried the first approach, but nothing changed. Then, I tried to log the results. I just drilled down level by level, until I finally got to where the data was being displayed.
After a while I found the problem: When I was sending the response, I was converting it to a string via .toString().
I fixed that and now it works brilliantly. Sorry for the false alarm.

Mongo/Mongoose Invalid atomic update value error

I am trying to write to write an update to a Mongo document using the Mongoose findOneAndUpdate function. Essentially, I have a document that has an array of another Schema in it, and when I attempt to append more of those schema type, I get the following error:
[Error: Invalid atomic update value for $__. Expected an object, received object]
I'm having a hard time figuring out what this error even means, much less what its source is.
The data I'm attempting to update is as follows:
{ section_id: 51e427ac550dabbb0900000d,
version_id: 7,
last_editor_id: 51ca0c4b5b0669307000000e,
changelog: 'Added modules via merge function.',
committed: true,
_id: 51e45c559b85903d0f00000a,
__v: 0,
modules:
[ { orderId: 0,
type: 'test',
tags: [],
data: [],
images: [],
content: ["Some Content Here"] },
{ orderId: 1,
type: 'test',
tags: [],
data: [],
images: [],
content: ["Some Content Here"] },
{ orderId: 2,
type: 'test',
tags: [],
data: [],
images: [],
content: ["Some Content Here"] },
{ orderId: 3,
type: 'test',
tags: [],
data: [],
images: [],
content: ["Some Content Here"] },
{ orderId: 4,
type: 'test',
tags: [],
data: [],
images: [],
content: ["Some Content Here"] },
{ orderId: 5,
type: 'test',
tags: [],
data: [],
images: [],
content: ["Some Content Here"] } ] }
The only difference is that when I retrieve it, there are three fewer modules, and I append some new ones to the array.
Would love to hear any thoughts, at least as to what the error means!
This is probably because the updated object is still a Mongoose object.
Try to convert it to a JS object before the findOneAndUpdate
object = object.toString()
And delete any potential ID attribute
delete object._id
Or simply
object = object.toObject();
I had the same problem and it turned out I was using $push incorrectly. I was doing
{$push:thing_to_push}
But it needed to be
{$push:{list_to_push_into:thing_to_push}}
#Magrelo and #plus led me to an answer that worked. Something like:
MyModel.findOneAndUpdate({ section_id: '51e427ac550dabbb0900000d' }, mongooseObject.toObject(), { upsert: true }, function(err, results) {
//...
});
Try passing update parameter value as string instead of mongoose model object. I was getting same error when I use to pass model object. Below is the code difference.
Code that was having issue:
updateUser: function(req, res) {
**var updatedUserModel = new Users(req.body);**
Users.findOneAndUpdate({tpx_id:req.params.id}, {$set:**updatedUserModel**}, function(err, User){
...
}
}
Working code:
updateUser: function(req, res) {
Users.findOneAndUpdate({tpx_id:req.params.id}, {$set:**req.body**}, function(err, User) {
...
}
I had the same issue. I ended up by using finOne() method
create a new one if no found, update the existing one if found.
I know there are two operations. but I just haven't find any way to do it in one step.
Another thing to check is if you are sending passing an array of changes to $set. The error message that I received when calling something like this:
db.products.update( { sku: "abc123" },
{ $set: [
{quantity: 500},
{instock: true}
]
}
)
Gave me the [Error: Invalid atomic update value for $set. Expected an object, received object]
Changing it to an object worked.
db.products.update( { sku: "abc123" },
{ $set: {
quantity: 500,
instock: true
}
}
)

Resources