inserting an objet with nested arrays in mongodb with moongose, nodejs - node.js

I am trying to insert an object like this in mongodb.
{
"client" : "Jhon"
"items" : [
[
"item": "whatever1",
],
[
"item": "whatever2",
]
]
}
I am using Mongoose, so i have a schema like this.
const itemSchema= new Schema({
item: { type: String, required: true },
})
const clientSchema = new Schema({
client: { type: String, required: true },
items: [itemSchema],
})
After i send the object to my nodejs server and create the document, i check the document created and "items" is an array that only contain _id: but nothing more.
I create the document like this.
createClient = (req, res) => {
console.log(req.body); // Here i check what is receiving the server.
clientModel.create(req.body).then(() => res.json('saved'));
}
In req.body, i checked that the server is receiving an object with empty arrays... is that normal? I am learning and i am new programing...

Your payload does not seem to be correct.
[ "item": "whatever1" ]
is not valid JSON and won't be parsed properly. Try changing your payload to:
{
"client": "Jhon"
"items": [
{ "item": "whatever1" },
{ "item": "whatever2" }
]
}

Related

Mongoose populate 3 deep nested schema with JSON response

I have a find() query that when executed, I can see the json with the nested schemas that I want to see except for the 'artista' attribute only displays the id, instead of the properties I want. See below:
{
"total": 1,
"ordenes": [
{
"artpieces": [
{
"_id": "60c1388f30316c02b9f6351f",
"artista": "60c055736c7ca511055a0e1a",
"nombre": "LILIES"
},
{
"_id": "60c12fca30316c02b9f63519",
"nombre": "GERNICA",
"artista": "60c136bf30316c02b9f6351b"
}
],
"_id": "60c414f9ea108a14ef75a9fb",
"precio": 3000,
"usuario": {
"_id": "609c0068e67e68",
"nombre": "Arturo Filio"
}
}
]
}
The query I use to get the json above:
const [total, ordenes] = await Promise.all([
Orden.countDocuments(),
Orden.find()
.populate("usuario", "nombre")
.populate("artpieces", ["nombre","artista","nombre"])
]);
res.json({
total,
ordenes
});
It's an order schema that has artpieces. Each artpiece (I called it 'producto'), has a name a genre, an author/artist and the user which the order belongs to.
my Schema for the orden.js:
const { Schema, model } = require('mongoose');
const OrdenSchema = Schema({
artpieces: [
{
type: Schema.Types.ObjectId,
required: true,
ref: 'Producto'
}
],
estado: {
type: Boolean,
default: true,
required: true
},
usuario: {
type: Schema.Types.ObjectId,
ref: 'Usuario',
required: true
},
precio: {
type: Number,
required: true
}
})
OrdenSchema.methods.toJSON = function () {
const { __v, estado, ...data} = this.toObject();
return data;
}
module.exports = model('Orden', OrdenSchema);
Last thing I want to mention, I know for a fact that I have the code necessary in the artista.js model to display the name of the artist because I have a similar query to display all the artpieces with each artpiece have a genre and an artist.
That example looks like so (to give context):
{
"total": 4,
"productos": [
{
"precio": 0,
"_id": "60c12fca30316c02b9f63519",
"nombre": "GERNICA",
"categoria": {
"_id": "60c04e3605d3c10ed10389e4",
"nombre": "NEO CUBISMO"
},
"artista": {
"_id": "60c136bf30316c02b9f6351b",
"nombre": "PICASSO"
},
"usuario": {
"_id": "609c8c0068e67e68",
"nombre": "Arturo Filio"
}
}
]
}
What am I doing wrong that I can't get my json result at the top look like the json at the bottom, where the artist attribute is?
Also just to point out, I have checked how to nest populate methods in order SO posts including the path and the ref to the Schema and still haven't been able to get the expected result.

mongoose find object into array of object

I'm new in mongoose and I'm trying to find user by code [user.test.test1.code] , any idea ?
Model :
const userSechema = new mongoose.Schema({
name: {
type: String,
required: true
},
test: [{}],
})
Data :
{
"_id": {
"$oid": "600020ab34742c2d34ae45e5"
},
"test": [{
"test1": {
"code": 11111
},
"test2": {
"code": 22222
}
}]
"name": "daniel"
}
query :
let regex = new RegExp(req.query.searchUserKey, 'i')
const users = await User.find({ $or: [{'name': regex },{'test.test1': { code : regex} }]})
-- Solution --
Thanks you guys, both answers is work for me
Is as simple as do "test.test1.code": 418816 into find query like this:
db.collection.find({
"test.test1.code": 418816
})
This query will give you all documents where exists test.test1.code with value 418816.
Note that this query return the whole document, not only the sub-document into the array. But I'm assuming by your post that a user is the document where exists the field name.
Example here
you can use $elemMatch, check the documentation
const users = await User.find(
{ test: { $elemMatch: { "test1.code": 418816 } } }
)

Mongoose findById

I am trying desperately to find a object stored with mongodb, with nodejs and mongoose.
The model of the object looks like:
const SimpleResourceSchema = new mongoose.Schema(
{
_id: String,
title: String,
objective: String,
url: String,
content: String,
active: Boolean,
type: String,
owner: String,
},
{
timestamps: true,
// _id: false,
}
);
export const SimpleResourceModel = mongoose.model<
SimpleResource & mongoose.Document
>('simpleResource', SimpleResourceSchema);
The query is made with 'id' parameter value '5f1da9737917360dd038bfc0':
return await SimpleResourceModel.findById(id).exec();
The data stored in mongodb is:
{
"_id": {
"$oid": "5f1da9737917360dd038bfc0"
},
"title": "Learn cooking",
"objective": "<p>Is the fridge empty ?</p>",
"content": "...",
"url": "..",
"active": true,
"type": "simple",
"owner": "5efceb2f63b75c1750846b0a",
"createdAt": {
"$date": "2020-07-26T16:04:03.806Z"
},
"updatedAt": {
"$date": "2020-07-26T16:04:03.806Z"
},
"__v": 0
}
I have looked around to get a solution, but have not found any solution to this roadblock.
Anyone can help ?
The main issue that when you define the schema you defined the id as string remove _id: String from schema definition. and it automatic be added.
If you want to add _id to typescript you can create interface
export interface SimpleResource extends Document {
_id: schema.Types.ObjectId,
...
}
then in model you directly add it but _id already defined in Document interface
and make sure that you install #types/mongoose
export const SimpleResourceModel = mongoose.model<SimpleResource>('simpleResource', SimpleResourceSchema);
Have you tried?
var ObjectId = require('mongoose').Types.ObjectId;
return await SimpleResourceModel.findById(new ObjectId(id)).exec();
i still get a null response when I try:
await SimpleResourceModel.findById(mongoose.Types.ObjectId(id)).exec()

Update mongodb document that has multiple arrays of embedded documents using mongoose

Say i have a document that looks like this:
{
"personId": 13998272,
"address": [
{
"addressType": "HOME",
"streetNo": 21,
"addressLine1": "LORRAINE AVENUE",
"addressLine2": "EDGEWATER",
"city": "KINGSTON",
"parish": "ST ANDREW",
"country": "JAMAICA",
"qScore": 0.9,
"modifiedDate": "2019-02-17 15:24:19"
}
],
"phone": [
{
"originalNumber": "+18767842983",
"phoneNumberIFormat": "+18768514679",
"phoneNumberLFormat": "8768514679",
"qualityScore": 0.8,
"dataSource": "PERSON",
"modifiedDate": "2018-12-17 09:42:31"
}
],
"email": [
{
"emailAddress": "neilagreen78#yahoo.com",
"dataSource": "FINACLE",
"qualityScore": 0.89,
"modifiedDate": "2018-12-17 09:38:41"
}
]
}
My schema is defined in the code snippet below for reference:
const contactSchema = new mongoose.Schema({
pid: Number,
address: [
new mongoose.Schema({
addressType: String,
streetNo: String,
addressLine1: String,
addressLine2: String,
city: String,
parish: String,
country: String,
qScore: String,
modifiedDate: String
})
],
phone: [
new mongoose.Schema({
originalNumber: String,
phoneNumberIFormat: String,
phoneNumberLFormat: String,
qualityScore: Number,
dataSource: String,
modifiedDate: String
})
],
email: [
new mongoose.Schema({
emailAddress: String,
dataSource: String,
qualityScore: Number,
modifiedDate: String
})
]
});
How would update each array of embedded documents without overwriting the others?
Say a request is with and address and email object but not phone, how would I handle that?
You can try this ..
Get the structure of the contact object then the check to see what properties were sent in the req.body and build out the query accordingly.
N.B: You must have some validation to check the request body to ensure no unwanted properties are sent. You can use a package like Joi
const getContact = await contact.findOne({ id: req.params.id });
let query = { $addToSet: {} };
for (let key in req.body) {
if (getContact[key] && getContact[key] !== req.body[key])// if the field we have in req.body exists, we're gonna update it
query.$addToSet[key] = req.body[key];
}
const contact = await Customer.findOneAndUpdate(
{ pid: req.params.id },
query,
{new: true}
);
With mongoose, you can use $push to push object to array. The query will be like:
(saving an address and an email, querying on pid)
db.getCollection("contactSchema").update({"pid":1}, {$push: {email: emailToPush,
address:addressToPush}})
{"pid":1} is the pid of the object you want to update;
{$push: {email: emailToPush, address:addressToPush}} is the object you want to push on each array
Then you have to filter the body of the request with a middleware or something like that. I usually use a middleware to check if the request is correct, like:
EDIT:
const buildQuery = (requestBody) => {
let query = {$push: {}};
Object.keys(requestBody).map(key => {
query.$push[key] = requestBody[key];
});
}
This will build your query object (the second parameter of the update function).

Upsert Using an Array Without Creating Duplicates

I'm having trouble 'upserting' to my array. The code below creates duplicates in my answers array which I definitely do not want and by now it's apparent $push will not work. I have tried using the different methodologies I see on SO for a while now but none are working for me. With this web app, users are allows to view a question on the website and respond with a 'yes' or 'no' response and they are allowed to change(upsert) their response at any one time meaning a sort of upsert takes place on the db at different times. How do get around this?
var QuestionSchema = Schema ({
title :String,
admin :{type: String, ref: 'User'},
answers :[{type: Schema.Types.Mixed, ref: 'Answer'}]
});
var AnswerSchema = Schema ({
_question :{type: ObjectId, ref: 'Question'},
employee :{type: String, ref: 'User'},
response :String,
isAdmin :{type: Boolean, ref: 'User'}
})
var UserSchema = Schema({
username : String,
isAdmin : {type: Boolean, default: false}
});
module.exports = mongoose.model('Question', QuestionSchema);
module.exports = mongoose.model('Answer', AnswerSchema);
module.exports = mongoose.model('User', UserSchema);
Question.update(
{_id: req.body.id},
{$push: {answers: {_question: req.body.id,
employee: req.body.employee,
response: req.body.response, //this variable changes (yes/no/null)
isAdmin: req.body.isAdmin}}},
{safe: true, upsert: true},
function(err, model) {
}
);
As I see it you seem a little confused and it's reflected in your schema. You don't seem to fully grasp the differences between "embedded" and "referenced" since your schema is actually an invalid "mash" of the two techniques.
Probably best to walk you through both of them.
Embedded Model
So instead of the schema you have defined, you should in fact have something more like this:
var QuestionSchema = Schema ({
title :String,
admin :{type: String, ref: 'User'},
answers :[AnswerSchema]
});
var AnswerSchema = Schema ({
employee :{type: String, ref: 'User'},
response :String,
isAdmin :{type: Boolean, ref: 'User'}
})
mongoose.model('Question', questionSchema);
NOTE: Question is the only actual model here. The AnswerSchema is completely "embedded".
Note the clear definition of the "schema" where the "answers" property in Question is defined as an "array" of AnswerSchema. This is how you do embedding and keep control of the types within the object inside the array.
As for the update, there is a clear logic pattern but you are simply not enforcing it. All you need to do is "tell" the update that you do not want to "push" a new item if something for that "unique" "employee" in the array already exists.
Also. This is NOT and "upsert". Upsert implies "creating a new one", which is different to what you want. You want to "push" to the array of an "existing" Question. If you leave "upsert" on there, then something not found creates a new Question. Which is of course wrong here.
Question.update(
{
"_id": req.body.id,
"answers.employee": { "$ne": req.body.employee },
}
},
{ "$push": {
"answers": {
"employee": req.body.employee,
"response": req.body.response,
"isAdmin": req.body.isAdmin
}
}},
function(err, numAffected) {
});
That will look to check that the "unique" "employee" in the array members already and will only $push where it is not already there.
As a bonus, if you intend to allow the user to "change their answer" then we do this incantation with .bulkWrite():
Question.collection.bulkWrite(
[
{ "updateOne": {
"filter": {
"_id": req.body.id,
"answers.employee": req.body.employee,
},
"update": {
"$set": {
"answers.$.response": req.body.response,
}
}
}},
{ "updateOne": {
"filter": {
"_id": req.body.id,
"answers.employee": { "$ne": req.body.employee },
},
"update": {
"$push": {
"answers": {
"employee": req.body.employee,
"response": req.body.response,
"isAdmin": req.body.isAdmin
}
}
}
}}
],
function(err, writeResult) {
}
);
This effectively puts two updates in one. The first to attempt to alter an existing answer and $set the response at the matched position, and the second to attempt to add a new answer where one was not found on the question.
Referenced Model
With a "referenced" model you actually have the real members of the Answer within their own collection. So instead the schema is defined like this:
var QuestionSchema = Schema ({
title :String,
admin :{type: String, ref: 'User'},
answers :[{ type: Schema.Types.ObjectId, ref: 'Answer' }]
});
var AnswerSchema = Schema ({
_question :{type: ObjectId, ref: 'Question'},
employee :{type: String, ref: 'User'},
response :String,
isAdmin :{type: Boolean, ref: 'User'}
})
mongoose.model('Answer', answerSchema);
mongoose.model('Question', questionSchema);
N.B The other ref's here to User such as :
employee :{type: String, ref: 'User'},
isAdmin :{type: Boolean, ref: 'User'}
These are also really incorrect, and also should be of Schema.Type.ObjectId as they will "reference" the actual _id field of User. But this is actually outside of the scope of this question as asked, so if you still don't grasp that after this read, then Ask a New Question so someone can explain. On with the rest of the answer.
That's the "general" shape of the schema though, with the important thing being the "ref" to the 'Anwser' "model", which is by the registered name. You can optionally just use your "_question" field in modern mongoose versions with a "virtual", but I'm skipping over "Adavanced Usage" for now and keeping it simple with an array of "references" still in the Question model.
In this case, since the Answer model is actually in it's own "collection", then the operations actually become "upserts". Where we only want to "create" when there is no "employee" response to the given "_question" id.
Also demonstrating with a Promise chain instead:
Answer.update(
{ "_question": req.body.id, "employee": req.body.employee },
{
"$set": {
"response": req.body.reponse
},
"$setOnInsert": {
"isAdmin": req.body.isAdmin
}
},
{ "upsert": true }
).then(resp => {
if ( resp.hasOwnProperty("upserted") ) {
return Question.update(
{ "_id": req.body.id, "answers": { "$ne": resp.upserted[0]._id },
{ "$push": { "answers": resp.upserted[0]._id } }
).exec()
}
return;
}).then(resp => {
// either undefined where it was not an upsert or
// the update result from Question where it was
}).catch(err => {
// something
})
This is actually a simple statement since "when matched" we want to change the "response" data with the payload of the request, and really only when "upserting" or "creating/inserting" is when we actually change other data such as the "employee" ( which is always implied for create as part of the query expression ) and the "isAdmin" which clearly should not change with each update request we then explicitly use $setOnInsert so it only writes those two fields on an actual "create".
In the "Promise Chain" we actually look to see if the update request to Answer actually resulted in an "upsert", and when it does we want to append to the array of Question where it does not already exist. In much the same way as the "embedded" example, it's best to look to see if the array actually has the item before modifying with the "update". Alternately you could $addToSet here and just let the query match the Question by _id. To me though, that's a wasted write.
Summary
Those are your different approaches to how you handle this. Each has their own use cases for which you can see a general summary some other answers of mine in:
Mongoose populate vs object nesting which is an overview of the different approaches and reasons behind them
How to Model a “likes” voting system with MongoDB which gives a bit more detail on the "unique array upserts" technique for "embedded" models.
Not "required" reading, but it may help expand your insight into which approach is best for your particular case.
Working Example
Copy these and put them in a directory and do an npm install to install local dependencies. The code will run and create the collections in the database making the alterations.
Logging is turned on with mongoose.set(debug,true) so you should look at the console output and see what it does, along with the resulting collections where answers will be recorded to the related questions, and overwritten instead of "duplicating" where that was also the intent.
Change the connection string if you have to. But that is all you should change in this listing for it's purpose. Both approaches described in the answer are demonstrated.
package.json
{
"name": "colex",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"async": "^2.4.1",
"mongodb": "^2.2.29",
"mongoose": "^4.10.7"
}
}
index.js
const async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema,
ObjectId = require('mongodb').ObjectID
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
mongoose.connect('mongodb://localhost/coltest');
const userSchema = new Schema({
username: String,
isAdmin: { type: Boolean, default: false }
});
const answerSchemaA = new Schema({
employee: { type: Schema.Types.ObjectId, ref: 'User' },
response: String,
});
const answerSchemaB = new Schema({
question: { type: Schema.Types.ObjectId, ref: 'QuestionB' },
employee: { type: Schema.Types.ObjectId, ref: 'User' },
response: String,
});
const questionSchemaA = new Schema({
title: String,
admin: { type: Schema.Types.ObjectId, ref: 'User' },
answers: [answerSchemaA]
});
const questionSchemaB = new Schema({
title: String,
admin: { type: Schema.Types.ObjectId, ref: 'User' },
answers: [{ type: Schema.Types.ObjectId, ref: 'AnswerB' }]
});
const User = mongoose.model('User', userSchema);
const AnswerB = mongoose.model('AnswerB', answerSchemaB);
const QuestionA = mongoose.model('QuestionA', questionSchemaA);
const QuestionB = mongoose.model('QuestionB', questionSchemaB);
async.series(
[
// Clear data
(callback) => async.each(mongoose.models,(model,callback) =>
model.remove({},callback),callback),
// Create some data
(callback) =>
async.each([
{
"model": "User",
"object": {
"_id": "594a322619ddbd437193c759",
"name": "Admin",
"isAdmin": true
}
},
{
"model": "User",
"object": {
"_id": "594a323919ddbd437193c75a",
"name": "Bill"
}
},
{
"model": "User",
"object": {
"_id": "594a327b19ddbd437193c75b",
"name": "Ted"
}
},
{
"model": "QuestionA",
"object": {
"_id": "594a32f719ddbd437193c75c",
"admin": "594a322619ddbd437193c759",
"title": "Question A Model"
}
},
{
"model": "QuestionB",
"object": {
"_id": "594a32f719ddbd437193c75c",
"admin": "594a322619ddbd437193c759",
"title": "Question B Model"
}
}
],(data,callback) => mongoose.model(data.model)
.create(data.object,callback),
callback
),
// Submit Answers for Users - Question A
(callback) =>
async.eachSeries(
[
{
"_id": "594a32f719ddbd437193c75c",
"employee": "594a323919ddbd437193c75a",
"response": "Bills Answer"
},
{
"_id": "594a32f719ddbd437193c75c",
"employee": "594a327b19ddbd437193c75b",
"response": "Teds Answer"
},
{
"_id": "594a32f719ddbd437193c75c",
"employee": "594a323919ddbd437193c75a",
"response": "Bills Changed Answer"
}
].map(d => ([
{ "updateOne": {
"filter": {
"_id": ObjectId(d._id),
"answers.employee": ObjectId(d.employee)
},
"update": {
"$set": { "answers.$.response": d.response }
}
}},
{ "updateOne": {
"filter": {
"_id": ObjectId(d._id),
"answers.employee": { "$ne": ObjectId(d.employee) }
},
"update": {
"$push": {
"answers": {
"employee": ObjectId(d.employee),
"response": d.response
}
}
}
}}
])),
(data,callback) => QuestionA.collection.bulkWrite(data,callback),
callback
),
// Submit Answers for Users - Question A
(callback) =>
async.eachSeries(
[
{
"_id": "594a32f719ddbd437193c75c",
"employee": "594a323919ddbd437193c75a",
"response": "Bills Answer"
},
{
"_id": "594a32f719ddbd437193c75c",
"employee": "594a327b19ddbd437193c75b",
"response": "Teds Anwser"
},
{
"_id": "594a32f719ddbd437193c75c",
"employee": "594a327b19ddbd437193c75b",
"response": "Ted Changed it"
}
],
(data,callback) => {
AnswerB.update(
{ "question": data._id, "employee": data.employee },
{ "$set": { "response": data.response } },
{ "upsert": true }
).then(resp => {
console.log(resp);
if (resp.hasOwnProperty("upserted")) {
return QuestionB.update(
{ "_id": data._id, "employee": { "$ne": data.employee } },
{ "$push": { "answers": resp.upserted[0]._id } }
).exec()
}
return;
}).then(() => callback(null))
.catch(err => callback(err))
},
callback
)
],
(err) => {
if (err) throw err;
mongoose.disconnect();
}
)
Here was my quick work around before Neill updated his answer (I used a $pull & $push). Works just as his but I'll mark his correct as I believe it's more efficient.
Question.update(
{_id: req.body.id},
{$pull: {answers: { employee: req.body.employee}}},
{safe: true, multi:true, upsert: true},
function(err, model) {
}
);
Question.update(
{_id: req.body.id},
{$push: {answers: {_question: req.body.id,
employee: req.body.employee,
response: req.body.response,
isAdmin: req.body.isAdmin}}},
{safe: true, upsert: true},
function(err, model) {
}
);

Resources