So I'm trying to retrieve multiple docs using Mongoose and render them using the HBS view engine. Unfortunatley, the only way I know how to render the view is to call res.render() inside the callback to the find() function used to retrieve documents from the MongoDB database. As such I can only retrieve one doc at a time and I'd like to know how to save multiple docs to variables and then render using res.render(). Anybody know how to do this? Router code below.
Basically, I'm pulling from multiple collections and want to know how to save the output of the find() function as variables and then pass them to the res.render() function to render it. You can see my hammed attempt below to save the results as variables which only returns a promise.
index.js:
var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/test');
var Schema = mongoose.Schema;
var moment = require('moment');
var supportForumListSchema = new Schema({
title: String,
description: String,
numPosts: Number,
dateCreated: Date
}, {collection: 'support-forum-listing'});
var artworkForumListSchema = new Schema({
title: String,
description: String,
numPosts: Number,
dateCreated: Date
}, {collection: 'artwork-forum-listing'});
var supportForumList = mongoose.model('supportForumList', supportForumListSchema);
var artworkForumList = mongoose.model('artworkForumList', artworkForumListSchema);
tempDate = new Date(1542325638042);
currentDate = new Date(1542333003752);
console.log("current date:" + currentDate.toDateString());
tempHoursAgo = currentDate - tempDate;
tempHoursAgo = tempHoursAgo / (1000*60*60);
tempDateString = tempHoursAgo.toFixed(0); // could use Number(string) to convert back to number
console.log(tempDateString + "hours ago");
// temp new posts array
var newPosts = [
{postTitle: "Need help!", numViews: 1, datePosted: tempDateString},
{postTitle: "Unknown identifier", numViews: 0, datePosted: tempDateString},
{postTitle: "Messing up my normals", numViews: 3, datePosted: tempDateString},
{postTitle: "Anyone able to help?", numViews: 3, datePosted: tempDateString}
];
/* GET home page. */
router.get('/', function(req, res, next) {
artworkDoc = artworkForumList.find().then(function(doc){
return doc;
});
console.log(artworkDoc);
supportForumList.find().then(function(supportDoc){
res.render('index', { title: 'Home - 3D Artists Forum', headerTitle: "3D Artist Forums", supportForumList: supportDoc , artworkForumList: artworkDoc, latestPosts: newPosts});
});
});
module.exports = router;
Remember that requests are async calls, so you should chain then in order to get the desired result, otherwise " supportForumList.find()" might respond before artworkForumList.find()
Try this way
router.get('/', function(req, res, next) {
artworkDoc = artworkForumList.find().then(function(doc){
return doc;
}).then( doc => {
supportForumList.find().then(function(supportDoc){
res.render('index', { title: 'Home - 3D Artists Forum', headerTitle: "3D Artist Forums", supportForumList: supportDoc , artworkForumList: doc, latestPosts: newPosts});
});;
});
In this approach, supportForumList.find() is executed only when artworkForumList.find() has responded and you have data in the "doc" variable, and then that data is passed as a parameter.
Hope it Helps
Or same code with arrow function:
router.get('/', (req, res)=> {
artworkForumList.find()
.then(doc =>
supportForumList.find()
.then(supportDoc => res.render(title: 'Home - 3D Artists Forum', headerTitle: "3D Artist Forums", , { supportForumList: supportDoc, artworkForumList: doc, latestPosts: newPosts })))
}
Related
I'm working on a web application for my company to view a database of customers and their data using MongoDB, Mongoose, and Express. Our company resells used copiers/printers and also provides maintenance contracts for machines. I want to save each customer as a document, with machines as separate linked documents.
I have models, controllers, and routes set up for customers and machines. I am getting the following error when trying to delete a machine from it's customer:
Customer.findByIdAndUpdate is not a function
TypeError: Customer.findByIdAndUpdate is not a function at module.exports.deleteMachine (C:\controllers\machines.js:21:20) at C:\utils\catchAsync.js:3:9 at Layer.handle [as handle_request] (C:\node_modules\express\lib\router\layer.js:95:5) at next (C:\node_modules\express\lib\router\route.js:144:13) at module.exports.getCustomer (C:\middleware.js:15:5) at processTicksAndRejections (node:internal/process/task_queues:96:5)
My code is as follows:
Controller for Machines:
const Customer = require('../models/customer');
const Machine = require('../models/machine');
module.exports.deleteMachine = async (req, res) => {
const { id, machineId } = req.params;
await Customer.findByIdAndUpdate(id, { $pull: { machines: machineId } });
await Machine.findByIdAndDelete(machineId);
req.flash('success', 'Machine has been deleted');
res.redirect(`/customers/${id}`);
};
Route for Machines:
router.delete('/:machineId', getCustomer, catchAsync(machines.deleteMachine));
the "getCustomer" middleware is as follows - its only purpose is to ensure a valid customer is being requested and to set the "foundCustomer" to make my life easier elsewhere. I don't think it is the issue, but I'm including it just for clarity:
module.exports.getCustomer = async (req, res, next) => {
const { id } = req.params;
const customer = await Customer.findById(id).populate({ path: 'machines' });
if (!customer) {
req.flash('error', 'Sorry, that customer cannot be found!');
return res.redirect('/customers');
}
res.locals.foundCustomer = customer;
next();
};
The relevant routes have been set as follows in my app.js:
const customerRoutes = require('./routes/customers');
const machineRoutes = require('./routes/machines');
app.use('/customers', customerRoutes);
app.use('/customers/:id/machines', machineRoutes);
I haven't run into any issues with other machine routes, so I'm not sure why this one is throwing an error. This application is actually the second version that I've made, and the first version uses the exact same code, with no issue. So I'm super stumped.
Any help is greatly appreciated!
Customer Model -
const customerSchema = new Schema({
customer: String,
customerID: String,
category: {
type: String,
enum: ['contracted', 'billable']
},
contacts: [contactSchema],
address: String,
city: String,
state: String,
zip: String,
county: String,
machines: [
{
type: Schema.Types.ObjectId,
ref: 'Machine'
}
],
notes: [noteSchema]
});
I'm a dummy. I exported the Customer model as part of an array of exports like this:
const Customer = mongoose.model('Customer', customerSchema);
module.exports = {
Customer: Customer,
Note: Note,
Contact: Contact
};
When requiring the model in my Machine controller I had it formatted as:
const Customer = require('../models/customer');
To get it working correctly I needed to require it like this:
const { Customer } = require('../models/customer');
After making that change everything is working correctly, and I can move on with my life/application.
I'm building an app where a user logs in and can create a grocery list on their account (there are more things they can do like create recipes, but this is the example I want to use). Right now I have it so everybody who logs in sees the same list. But I want each user to be able to log in and view their own grocery list that they made. I'm assuming the logic is literally like logging into a social media site and viewing YOUR profile, not somebody else's.
I'm using mongoDB/mongoose and I just read about the populate method as well as referencing other schemas in your current schema. Here is my schema for the list:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Create item schema
const GroceryListItemSchema = new Schema({
item: {
type: String,
required: [true, 'Item field is required']
},
userId: {
type: mongoose.Schema.Types.ObjectId, ref: "user",
}
});
// Create an Item model
const GroceryListItem = mongoose.model('groceryListItem', GroceryListItemSchema);
module.exports = GroceryListItem;
And here is the post request to add a list item:
//POST request for shopping list
router.post("/list", checkToken, (req, res, next) => {
// Add an item to the database
const groceryListItem = new GroceryListItem({
item: req.body.item,
userId: ???
})
groceryListItem.save()
.then((groceryListItem) => {
res.send(groceryListItem);
})
.catch(next);
});
Here is my userModel - not sure if this is necessary to show:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const UserSchema = new Schema({
username: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
password2: {
type: String,
required: true,
},
});
const User = mongoose.model("users", UserSchema);
module.exports = User;
(in case anyone is wondering why the model is called "users"-- that's what I initially called it on accident and when I changed the name to "user" it errored out...so I changed it back.)
I am not sure how to add the userId when making an instance of the groceryListItem. In the mongoose docs (https://mongoosejs.com/docs/populate.html#saving-refs), they use the example of a Story and Person Schema. They reference each other, and then they create an instance of Person, calling it author. Then they grab the _id from author and reference it in their instance of Story, called story1. So that makes sense to me. But the only way they can do that is because author and story1 are located in the same file.
So it seems like what I should do is grab the user _id by saying userId: users._id. But my new User instance is in my user routes. And I'd rather not combine the two. Because then I'd have another list to combine as well so that would be my user routes, recipe routes, and shopping list routes all in one file and that would be extremely messy.
Anyone have any idea how I can make this work? It seems so simple but for some reason I cannot figure this out.
Thank you!!
EDIT - frontend API call:
handleSubmitItem = (e) => {
e.preventDefault();
const newItem = {
item: this.state.userInput,
};
authAxios
.post(`http://localhost:4000/list/${userId}`, newItem)
.then((res) => {
this.setState({ items: [...this.state.items, newItem] });
newItem._id = res.data._id;
})
.catch((err) => console.log(err));
this.setState({ userInput: "" });
};
Here you can simply pass in the user ID in the POST request params. The POST URL in the frontend should look like this; {localhost:9000/like/${userID}}
You can get the user ID at the express backend like this;
router.post("/list/:id", checkToken, (req, res, next) => {
// Add an item to the database
const groceryListItem = new GroceryListItem({
item: req.body.item,
userId: req.params.id
})
groceryListItem.save()
.then((groceryListItem) => {
res.send(groceryListItem);
}).catch(next);
});
I want to save complex data, ie array of objects to mongoose. I have tried few things but i couldn't save the data.
I defined my schema as above and i want to save array of objects that could have any level of nesting.
Schema
const mongoose = require('mongoose);
const PostSchema = new mongoose.Schema({
post: [{}]
});
let PostModel = mongoose.Model('Post', PostSchema)
The Data:
Here is the code I used to save the data
app.post('/saveData, async (req, res) => {
const response = await Post.create(req.body);
res.json({
data: response
});
});
app.listen(8008, () => {
console.log('server running);
});
The problem is that i cant retrieve the data. it returns array of objects equal to the number of saved array but with no data in it.
How can it be done?
This code works for me.
const PostModel = require('./Post'); //declare your model
app.post('/saveData', async (req, res) => {
const objModel = new PostModel();
objModel.post = req.body; //assign the data post array.
const response = await objModel.save();
res.json({
data: response
});
});
Your post schema looks weird.
You have a collection for Posts and then within a posts schema, you have a posts array.
What is the point of that?
The post collection already is an "array" for posts.
// Perhaps you are looking for something like this.
const PostSchema = new mongoose.Schema({
title: String,
content: String,
level: Number,
footer: String,
author: ObjectId,// who wrote the post
comments: [{
user: ObjectId,
comment: String
}],
... createdAt, updatedAt etc
});
Your data structure doesnt seem to match your schema either.
e.g
await Post.create({posts: req.body});
I am writing post api using restify and mongodb with mongoose.
'use strict'
const Trending = require('../models/trending');
const trendingController = {
postTrending: (req, res, next)=>{
let data = req.body || {};
console.log(Trending);
let Trending = new Trending(data);
Trending.save(function(err) {
if (err) {
console.log(err)
return next(new errors.InternalError(err.message))
next()
}
res.send(201)
next()
})
}
}
here error is that Trending is not defined, I don't know why.. other similar controllers are working fine.
Trending is mongoose Model
model code
'use strict'
const mongoose = require('mongoose');
const timestamps = require('mongoose-timestamp');
const Schema = mongoose.Schema;
const TrendingSchema = new mongoose.Schema({
_id: Schema.Types.ObjectId,
headline: {
type: String,
required: true
},
description: String,
data: [
{
heading: String,
list: [String]
}
],
tags: [{ type: Schema.Types.ObjectId, ref: 'Tags' }]
});
TrendingSchema.plugin(timestamps);
const Trending = mongoose.model('Trending', TrendingSchema)
module.exports = Trending;
folder structure
controllers
--- trending.js
models
---trending.js
You are having this problem because of this line;
let Trending = new Trending(data);
You should avoid using the same variable name for two different things to prevent this kind of problem. Especially in this case where you are using uppercase letter for an object when you should use it only for classes.
Replace that line with;
let trending = new Trending(data);
The problem happens because let (and const) are block scoped so a variable with the same name but from an outer scope will be overridden. You then get undefined for this variable because you are referencing it in the same line you are declaring it, so it is in fact still undefined.
models/category.js
var mongoose = require('mongoose');
// Category Schema
var categorySchema = mongoose.Schema({
title: {
type: String
},
description: {
type: String
},
created_at: {
type: Date,
default: Date.now
}
});
var Category = module.exports = mongoose.model('Category', categorySchema);
// Get Categories
module.exports.getCategories = function(callback, limit) {
Category.find(callback).limit(limit).sort([['title', 'ascending']]);
}
routes/categories.js
var express = require('express');
var router = express.Router();
Category = require('../models/category.js');
router.get('/', function(req, res, next) {
Category.getCategories(function(err, categories) {
if (err) res.send(err);
res.render('categories',
{
title: 'Categories',
categories: categories
});
});
});
router.post('/add', function(req,res) {
res.send('Form Submitted');
});
module.exports = router;
I got a few questions about this code
a) how does the callback mechanism work from routes/categories.js when we pass that callback function to models/category.js in Category.find(callback). That seems bizarre to me since we are doing a whole res.render which becomes part of Category.find() ?
b) Where is limit specified?
c) Why isn't there var in front of Category = require('../models/category.js');
a) that is indeed what happens, and is good: res.render will not get called until the find() operation executes on the database and a result is sent back for the mongoose code to return to you. You want to run the callback function after you get the result for your query, and so calling res.render before would be much more bizarre.
b) in the documentation. http://mongoosejs.com/docs/api.html#model_Model.find yields a Query object, which may be synchronously (i.e. before the query is actually made to resolve at the database) further specified with where, limit, etc.
c) because someone got lazy. In this example it doesn't actually make a difference because without var (or const or let in modern JS) a variable declaration is tacked onto the local context, which in your file is the routes/categories.js module context, and because Categories is declared at the top of that scope, var doesn't change where the variable ends up being bound. But it's lazy, and for good example code, that should have var in front of it.