Pushing items to a nested array from an express post request - node.js

Below is the schema for my monthly budgets collection. Each of them is assigned a year and a month. Each budget has a multiple categories and sub-categories within each of them. I have decided to store them in an array.
var {mongoose} = require('../db/mongoose');
var mb = new mongoose.Schema({
month: String,
categories: [{
name: String,
sub_categories: [{
name: String,
amount: Number
}]
}]
});
var Monthly_Budgets = mongoose.model('Monthly_Budgets', mb);
module.exports = {
Monthly_Budgets
};
Below is the post express post request
app.post('/monthlyBudgets', (req, res) => {
var sub_categories = req.body.categories.sub_categories;
var categories = [{
name : req.body.categories.name,
sub_categories
}]
var budgets = new Monthly_Budgets({
month : req.body.month,
year: req.body.year
})
budgets.categories = categories;
budgets.save().then((docs) => {
res.send(docs);
console.log(docs);
}).catch((e) => res.status(404).send(e));
})
When I send the post request from postman, it does not result in an error, but it gives this:
{
"_id" : ObjectId("5b8a3280924c2d0dea15a1df"),
"month" : "September",
"year" : 2018,
"categories" : [
{
"_id" : ObjectId("5b8a3280924c2d0dea15a1e0"),
"sub_categories" : []
}
],
"__v" : 0
}
I can't seem to figure out the issue. Please help.

Related

how to get aggregate from fields in mongoose using node js from another models

I have two models schema productPerDay and productPerMonth
// Model 1 that input data every day
const productPerDay = new mongoose.Schema({
product : String,
price : Number,
month : {
type : String
default : () => moment(Date.now()).tz('Asia/Jakarta').locale('id').format('MMMM')
}
}, {
timestamps : {
currentTime: () => moment(Date.now()).tz('Asia/Jakarta').locale('id').format('YYYY-MM-Do LT')
}
});
//Model 2 That input data every month and get aggregate for total price field from price data model1 that input every day
const productPerMonth = new mongoose.Schema({
product : String,
totalPrice : Number,
month : {
default : () => moment(Date.now()).tz('Asia/Jakarta').locale('id').format('MMMM')
}
}, {
timestamps : {
currentTime: () => moment(Date.now()).tz('Asia/Jakarta').locale('id').format('YYYY-MM-Do LT')
}
});
the results I expect are like this
//From Model 1
[
{
"product" : "book",
"price" : 5000,
"month" : "May"
},
{
"product" : "book",
"price" : 10000,
"month" : "May"
}
{
"product" : "pen",
"price" : 100,
"month" : "June"
},
{
"product" : "pen",
"price" : 200,
"month" : "June"
}
]
//From Model 2
[
{
"product" : "book",
"month" : "may",
"totalPrice" : 15000
},
{
"product" : "pen",
"month" : "June",
"totalPrice" : 300
}
]
I try to post and get the result from Model 1 in Nodejs like this
// router to post and get data each day
router.post('/productPerDay', async (req,res) => {
const product = new ProductPerDayModels({});
try {
await ProductPerDayModels.save();
res.status(201).json(product);
} catch (e) {
res.status(400).send(e);
}
});
router.get('/productPerDay', async (req,res) => {
try {
const product = await ProductPerDayModels.find([]);
res.json(product);
} catch (e) {
res.status(400).send(e);
}
});
I want to get data for totalPrice field in model 2 from every price data filled in Model 1 And the data is added together for every month the same
//router 2 to get and post each month
router.post('/productPerMonth', async (req,res) => {
const product = new ProductPerMonthModels({
...req.body,
totalPrice : //what should i do here to get totalPrice from price fields in model 1?
});
try {
await ProductPerMonthModels.save();
res.status(201).json(product);
} catch (e) {
res.status(400).send(e);
}
});
router.get('/productPerMonth', async (req,res) => {
try {
const product = await ProductPerMonthModels.find([]);
res.json(product);
} catch (e) {
res.status(400).send(e);
}
});
Please guide me to resolve this or tell my mistake in resolving it. thank you

Response output not showing entire information present in mongoose schema nodejs

I have designed a Mongoose schema like this :
const metricsSchema = mongoose.Schema({
_id : mongoose.Schema.Types.ObjectId,
level : String,
details: {
demo: String,
full: String
}
});
Also, I have handled the response as such :
router.post('/',(req, res, next)=>{
const metrics = new Metrics({
_id : new mongoose.Types.ObjectId(),
level : req.body.level,
details:{
demo: req.body.demo,
full: req.body.full
}
});
res.status(201).json({
metrics: metrics
})
});
However, when I use Postman to post JSON data like this :
{
"level" :"schema" ,
"details":{
"demo" : "2465",
"full" : "1211234"
}
}
I get output like this :
{
"metrics": {
"_id": "5e09c156b0ce8a4a54a3ecca",
"level": "schema"
}
}
I do not get the rest of the output : demo and full in the response json. I wish to get the output like this :
{
"metrics": {
"_id": "5e09c156b0ce8a4a54a3ecca",
"level": "schema"
"details": {
"demo": "2465",
"full": "1211234"
}
}
}
Update: I found one solution in which the Mongoose schema was divided into two parts :
const detailsSchema = mongoose.Schema({
_id : mongoose.Schema.Types.ObjectId,
demo: String,
full: String
});
mongoose.model('Details',detailsSchema );
const metricsSchema = mongoose.Schema({
_id : mongoose.Schema.Types.ObjectId,
level : String,
details: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Details'
}
});
However, this did not work as well.
You have to change the code as below:
router.post('/',(req, res, next)=>{
const metrics = new Metrics({
_id : new mongoose.Types.ObjectId(),
level : req.body.level,
details:{
demo: req.body.details.demo, <----- see the change here
full: req.body.details.full
}
});
res.status(201).json({
metrics: metrics
})
});

How to populate documents with unlimited nested levels using mongoose

I'm designing a web application that manages organizational structure for parent and child companies. There are two types of companies: 1- Main company, 2 -Subsidiary company.The company can belong only to one company but can have a few child companies. My mongoose Schema looks like this:
var companySchema = new mongoose.Schema({
companyName: {
type: String,
required: true
},
estimatedAnnualEarnings: {
type: Number,
required: true
},
companyChildren: [{type: mongoose.Schema.Types.ObjectId, ref: 'Company'}],
companyType: {type: String, enum: ['Main', 'Subsidiary']}
})
module.exports = mongoose.model('Company', companySchema);
I store all my companies in one collection and each company has an array with references to its child companies. Then I want to display all companies as a tree(on client side). I want query all Main companies that populates their children and children populate their children and so on,with unlimited nesting level. How can I do that? Or maybe you know better approach. Also I need ability to view,add,edit,delete any company.
Now I have this:
router.get('/companies', function(req, res) {
Company.find({companyType: 'Main'}).populate({path: 'companyChildren'}).exec(function(err, list) {
if(err) {
console.log(err);
} else {
res.send(list);
}
})
});
But it populates only one nested level.
I appreciate any help
You can do this in latest Mongoose releases. No plugins required:
const async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
const uri = 'mongodb://localhost/test',
options = { use: MongoClient };
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
function autoPopulateSubs(next) {
this.populate('subs');
next();
}
const companySchema = new Schema({
name: String,
subs: [{ type: Schema.Types.ObjectId, ref: 'Company' }]
});
companySchema
.pre('findOne', autoPopulateSubs)
.pre('find', autoPopulateSubs);
const Company = mongoose.model('Company', companySchema);
function log(data) {
console.log(JSON.stringify(data, undefined, 2))
}
async.series(
[
(callback) => mongoose.connect(uri,options,callback),
(callback) =>
async.each(mongoose.models,(model,callback) =>
model.remove({},callback),callback),
(callback) =>
async.waterfall(
[5,4,3,2,1].map( name =>
( name === 5 ) ?
(callback) => Company.create({ name },callback) :
(child,callback) =>
Company.create({ name, subs: [child] },callback)
),
callback
),
(callback) =>
Company.findOne({ name: 1 })
.exec((err,company) => {
if (err) callback(err);
log(company);
callback();
})
],
(err) => {
if (err) throw err;
mongoose.disconnect();
}
)
Or a more modern Promise version with async/await:
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.set('debug',true);
mongoose.Promise = global.Promise;
const uri = 'mongodb://localhost/test',
options = { useMongoClient: true };
const companySchema = new Schema({
name: String,
subs: [{ type: Schema.Types.ObjectId, ref: 'Company' }]
});
function autoPopulateSubs(next) {
this.populate('subs');
next();
}
companySchema
.pre('findOne', autoPopulateSubs)
.pre('find', autoPopulateSubs);
const Company = mongoose.model('Company', companySchema);
function log(data) {
console.log(JSON.stringify(data, undefined, 2))
}
(async function() {
try {
const conn = await mongoose.connect(uri,options);
// Clean data
await Promise.all(
Object.keys(conn.models).map(m => conn.models[m].remove({}))
);
// Create data
await [5,4,3,2,1].reduce((acc,name) =>
(name === 5) ? acc.then( () => Company.create({ name }) )
: acc.then( child => Company.create({ name, subs: [child] }) ),
Promise.resolve()
);
// Fetch and populate
let company = await Company.findOne({ name: 1 });
log(company);
} catch(e) {
console.error(e);
} finally {
mongoose.disconnect();
}
})()
Produces:
{
"_id": "595f7a773b80d3114d236a8b",
"name": "1",
"__v": 0,
"subs": [
{
"_id": "595f7a773b80d3114d236a8a",
"name": "2",
"__v": 0,
"subs": [
{
"_id": "595f7a773b80d3114d236a89",
"name": "3",
"__v": 0,
"subs": [
{
"_id": "595f7a773b80d3114d236a88",
"name": "4",
"__v": 0,
"subs": [
{
"_id": "595f7a773b80d3114d236a87",
"name": "5",
"__v": 0,
"subs": []
}
]
}
]
}
]
}
]
}
Note that the async parts are not actually required at all and are just here for setting up the data for demonstration. It's the .pre() hooks that allow this to actually happen as we "chain" each .populate() which actually calls either .find() or .findOne() under the hood to another .populate() call.
So this:
function autoPopulateSubs(next) {
this.populate('subs');
next();
}
Is the part being invoked that is actually doing the work.
All done with "middleware hooks".
Data State
To make it clear, this is the data in the collection which is set up. It's just references pointing to each subsidiary in plain flat documents:
{
"_id" : ObjectId("595f7a773b80d3114d236a87"),
"name" : "5",
"subs" : [ ],
"__v" : 0
}
{
"_id" : ObjectId("595f7a773b80d3114d236a88"),
"name" : "4",
"subs" : [
ObjectId("595f7a773b80d3114d236a87")
],
"__v" : 0
}
{
"_id" : ObjectId("595f7a773b80d3114d236a89"),
"name" : "3",
"subs" : [
ObjectId("595f7a773b80d3114d236a88")
],
"__v" : 0
}
{
"_id" : ObjectId("595f7a773b80d3114d236a8a"),
"name" : "2",
"subs" : [
ObjectId("595f7a773b80d3114d236a89")
],
"__v" : 0
}
{
"_id" : ObjectId("595f7a773b80d3114d236a8b"),
"name" : "1",
"subs" : [
ObjectId("595f7a773b80d3114d236a8a")
],
"__v" : 0
}
I think a simpler approach would be to track the parent since that is unique instead of tracking an array of children which could get messy. There is a nifty module called mongoose-tree built just for this:
var tree = require('mongoose-tree');
var CompanySchema = new mongoose.Schema({
companyName: {
type: String,
required: true
},
estimatedAnnualEarnings: {
type: Number,
required: true
},
companyType: {type: String, enum: ['Main', 'Subsidiary']}
})
CompanySchema.plugin(tree);
module.exports = mongoose.model('Company', CompanySchema);
Set some test data:
var comp1 = new CompanySchema({name:'Company 1'});
var comp2 = new CompanySchema({name:'Company 2'});
var comp3 = new CompanySchema({name:'Company 3'});
comp3.parent = comp2;
comp2.parent = comp1;
comp1.save(function() {
comp2.save(function() {
comp3.save();
});
});
Then use mongoose-tree to build a function that can get either the ancestors or children:
router.get('/company/:name/:action', function(req, res) {
var name = req.params.name;
var action = req.params.action;
Company.find({name: name}, function(err, comp){
//typical error handling omitted for brevity
if (action == 'ancestors'){
comp.getAncestors(function(err, companies) {
// companies is an array
res.send(companies);
});
}else if (action == 'children'){
comp.getChildren(function(err, companies) {
res.send(companies);
});
}
});
});

$unwind nested document and $match

I have a nested document which looks like:
var User = new Schema({
id: String,
position: [{
title: String,
applied:[{
candidate_id: String,
name: String
}],
}],
What I am looking to do is return all of the 'applied' subdocuments which match a certain 'candidate_id'
What I have so far:
app.get('/applied', function(req, res){
var position = "58dc2bd4e7208a3ea143959e";
User.aggregate(
{$unwind : "$position"},
{$unwind : "$position.applied"},
{$match:{'position.applied.candidate_id': position}}).exec(function (err, result) {
console.log(result);
});
res.render('applied', { title: 'applied',layout:'candidate'});
});
I have another function which returns all the positions that match, and that code works:
app.post('/search', function (req, res) {
var position = new RegExp(req.body.position, 'i');
var location = new RegExp(req.body.location, 'i');
User.aggregate(
{$unwind : "$position"},
{$match:{'position.title': position,'position.location':location}}).exec(function (err, result) {
console.log(result);
res.send({ results: result });
});
});
So basically I am struggling with getting a sub-sub-document. Any idea where I'm going wrong?
Sample data:
{
"_id" : ObjectId("58c2871414cd3d209abf5fc9"),
"position" : [
{
"_id" : ObjectId("58d6b7e11e793c9a506ffe8f"),
"title" : "Software Engineer",
"applied" : [
{
"candidate_id" : "58d153e97e3317291gd80087",
"name" : "Sample user"
},
{
"candidate_id" : "58d153e97e3317291fd99001",
"name" : "Sample User2"
}
]
},
{
"_id" : ObjectId("58c2871414cd3d209abf5fc0"),
"title" : "Software Engineer",
}
],
}
What is going on above is there 2 positions, one of which (first entry) has 2 applied candidates, What I need to do is return the nested object if it matches the mongoose query.
Your code seems fine to me I have implemented same and it works for me only possible issue can be that your position="58dc2bd4e7208a3ea143959e" it might be talking it as a string just convert it to objectId by using the following code and check it should work for you.
var mongoose = require('mongoose');
var position = mongoose.Types.ObjectId("58dc2bd4e7208a3ea143959e");
User.aggregate(
{$unwind : "$position"},
{$unwind : "$position.applied"},
{$match:{'position.applied.candidate_id': position}}).exec(function (err, result) {
console.log(result);
});
res.render('applied', { title: 'applied',layout:'candidate'});
});

Populating in array of object ids

My schema:-
var playlistSchema = new Schema({
name : {type:String,require:true},
videos : {type:[mongoose.Schema.Types.ObjectId],ref: 'Video'},
},{collection:'playlist'})
var Playlist = mongoose.model('Playlist',playlistSchema);
I have some data in the database as exapmple:-
{
"_id" : ObjectId("58d373ce66fe2d0898e724fc"),
"name" : "playlist1",
"videos" : [
ObjectId("58d2189762a1b8117401e3e2"),
ObjectId("58d217e491089a1164a2441f"),
ObjectId("58d2191062a1b8117401e3e4"),
ObjectId("58d217e491089a1164a24421")
],
"__v" : 0
}
And the schema of Video is :-
var videoSchema = new Schema({
name : {type:String,required:true},
createdAt : {type:String,default:new Date()},
isDisabled : {type:Boolean,default:false},
album : {type: mongoose.Schema.Types.ObjectId, ref: 'Album'}
},{collection:'video'})
var Video = mongoose.model('Video',videoSchema);
Now in order to get the name of the all the videos in playlist i am trying the code:-
var playlistModel = mongoose.model('Playlist');
let searchParam = {};
searchParam._id = req.params.pid;
playlistModel.findOne(searchParam)
.populate('[videos]')
.exec(function(err,found){
if(err)
throw err;
else{
console.log(found.videos[0].name);
}
})
But here i am getting the undefined result.I am not getting where i am wrong plzz anyone help me to short out this problem.
got the answer:-
just change the schema
var playlistSchema = new Schema({
name : {type:String,require:true},
videos : [{type:mongoose.Schema.Types.ObjectId,ref: 'Video'}],
},{collection:'playlist'})
var Playlist = mongoose.model('Playlist',playlistSchema);
and just use
.populate('videos')
instead of
.populate('[videos]')
const app = await this.appModel.findOne({ slug })
.populate({
path: 'category',
populate: {
path: 'subCategories',
model: 'Category',
select: { name: 1, slug: 1, _id: 0 }
},
});

Resources