Object not being parsed in ejs file - node.js

I'm building a movie database with MongoDB, node.js, and express. I have two routes currently, one that renders a page that displays all of the movies in the database, and another that displays information for an individual movie. The problem is when rendering the page for each individual movie with ejs, I am not able to parse the object that is passed, which holds things like the title, the year, and a few arrays of images.
When rendering the all movies page, I am able to parse the object and display it with ejs. So I am not sure what the difference between the two routes is.
Schema for Movie
const movieSchema = new mongoose.Schema({
name: String,
date: String,
production: String,
cast: String,
remarks: String,
lobbyCards: Array,
filmStills: [
{
url: String,
caption: String
}
],
behindStills: [
{
url: String,
caption: String
}
],
advertising: [
{
url: String,
caption: String
}
]
});
Here are the two routes:
All Movies
app.get('/movies', (req, res) => {
movie.find({}, (err, movies) => {
if (err) {
console.log(err);
} else {
res.render('movies', { movies: movies });
// console.log(movies);
}
});
});
Individual Movie
app.get('/movies/:name', (req, res) => {
movie.find({ name: req.params.name }, (err, movieData) => {
if (err) {
console.log(err);
} else {
res.render('show', { movieData: movieData });
}
});
});
If I console.log(movieData) it is logged as an array.
All Films Ejs file
Individual Film Ejs file
This is what renders from the All films page
All films
This is what renders from the Individual Film Page
Individual film

If you want to get a single document from the db, you probably want to use:
movie.findOne({ name: req.params.name }, (err, movieData) => { ... }
With .find() an array is returned even when only one document matches the query which is why the movie object cannot be displayed in your ejs.

Related

Trouble showing query results in a view using express, hbs and mongodb

I'm developing a simple CRUD project with Express, using Handlebars and connecting to a Mongo database. I have some trouble when trying to print the results of a query in a template.
I already have my DB populated, and my model is defined as follows:
let movieSchema = new Schema({
director: {
type: String,
required: true
},
title: String,
}, {
timestamps : true
});
module.exports = mongoose.model("Movie", movieSchema);
Documents in the database have more fields than the model defined in my code (year, genre, rate...).
I have a root where a template is rendered, with a list of all of the retrieved elements of the collection movies. When the route handler is defined as follows, it works perfectly, and when sending the response, the movies array is correctly filled with all of the movies retrieved from the database.
Movie.find()
.then(movies => {
console.log("movies: ", movies);
res.send({
title: "Movie list",
movies
});
})
.catch(err => {
res.send(err);
}
But if I convert the res.send to a res.render, in order to show all data in the corresponding view, I have only access from the template to the fields that are defined in the model:
Movie.find()
.then(movies => {
console.log("movies: ", movies);
//changed res.send to res.render
res.render("films", {
title: "Movie list",
movies
});
})
.catch(err => {
res.send(err);
}
If I create a deep copy of movies, then res.render allows the template to get all the parameters of the movies array:
Movie.find()
.then(movies => {
console.log("movies: ", movies);
//deep copy of movies:
let moviesCopy = JSON.parse(JSON.stringify(movies));
res.render("films", {
title: "Movie list",
movies: moviesCopy
});
})
.catch(err => {
res.send(err);
}
It also works fine if I define the Schema in strict mode set to false:
let movieSchema = new Schema({
director: {
type: String,
required: true
},
title: String,
}, {
timestamps : true,
//strict mode set to false:
strict: false
});
module.exports = mongoose.model("Movie", movieSchema);
I cannot understand why I can perfectly see all the fields of the documents from the database with find() when using res.send, but when I use res.render I can only see the fields defined in the schema.
Any idea?

How to make search results include subdocument using .find() in Node js with MongoDB

Good Day,
For context, I am using node js, express and MongoDB in my project.
I have this code:
router.get('/test3', async (req, res) => {
const cookies = req.cookies;
// eslint-disable-next-line dot-notation
const to = cookies['to'];
console.log.apply(to);
SearchDb.findOne({"to": to}, (err, item) => {
console.log(item);
});
res.json("see cnsole");
});
and it returns something like this on the terminal
School
{
_id: new ObjectId("619be4e74c6a2504334f4a4d"),
from: 'Home',
to: 'School',
step: [
{
_id: new ObjectId("61a733c0a7614d6ba7f91561"),
start: 'Home',
vehicle: 'Taxi',
end: 'School',
cost: 100,
ave_time: 20
}
]
}
However, I don't really want to use .findOne(), I want to use .find() in case there are multiple search results, when I replace .findOne with .find() though, this is what shows on the terminal
School
{
_id: new ObjectId("619be4e74c6a2504334f4a4d"),
from: 'Home',
to: 'School',
step: [ [Object] ]
}
The subdocument "step" now returns [ [Object] ], which makes it hard to do things like item.step.length since it will now return undefined.
What should be done to make .find() show the whole document including the subdocuments just like .findOne?
#Bsn try this here I'm sending your result on front try POSTMAN for get or browser in console you are getting [object] but in your actual result you will get whole array of an object :)
router.get("/test3", async (req, res) => {
const cookies = req.cookies;
// eslint-disable-next-line dot-notation
const to = cookies["to"];
console.log.apply(to);
try {
SearchDb.find((err, item) => {
res.json(item);
});
} catch (err) {
res.json(err);
}
});

MERN - update specific string in an array's object

I am using mongoose to connect my backend (Express) server to database. I want to do normal CRUD operations - but I am able to do it only for direct data in object, but I need to be able to access also array data.
Example of my model:
const LeewaySchema = new mongoose.Schema({
name: {
type: String,
},
shirt: [
{
name: String,
image: String,
},
],
With the following code I am able to update only name of the object, but I need to be able to update also name in shirt array
Here is working approach when changing name of object:
app.put('/update', async (req, res) => {
const updateName = req.body.updateName;
const id = req.body.id;
console.log(updateName, id);
try {
await ClosetModel.findById(id, (error, closetToUpdate) => {
closetToUpdate.name = updateName;
closetToUpdate.save();
});
} catch (err) {
console.log(err);
}
res.send('success');
});
And I tried the same with shirt array, just specifying the correct path
app.put('/update-shirt', async (req, res) => {
const updateShirtName = req.body.updateShirtName;
const id = req.body.id;
try {
await ClosetModel.findById(id, (error, closetToUpdate) => {
closetToUpdate.shirt.name = updateShirtName; // different path here
closetToUpdate.save();
});
} catch (err) {
console.log(err);
}
res.send('success');
});
The server crashes and /update-shirt conflicts with /update path
I am using the same route and frontend for READ
useEffect(() => {
axios
.get('http://localhost:8000/read')
.then((response) => {
setListOfClosets(response.data);
})
.catch(() => {
console.log('error');
});
}, []);
And update name function calling with button onClick:
const updateCloset = (id) => {
const updateName = prompt('Enter new data');
axios
.put('http://localhost:8000/update', {
updateName: updateName,
id: id,
})
.then(() => {
setListOfClosets(
listOfClosets.map((val) => {
return val._id === id
? {
_id: id,
name: updateName,
email: val.email,
}
: val;
})
);
});
};
I don't really know how to do update for shirt's name, I tried to copy paste and just change path and url of course, but it did not work.
The question doesn't actually describe what specific transformation (update) you are attempting to apply to the document. Without knowing what you are attempting to do, there is no way for us to help advise on how to do it.
Say, for example, that the document of interest looks like this:
{
_id: 1,
shirt: [
{ name: "first shirt", image: "path to first shirt" },
{ name: "second shirt", image: "path to second shirt" },
{ name: "third shirt", image: "path to third shirt" }
]
}
Also let's say that the application hits the /update-shirt endpoint with an id of 1 and a updateShirtName of "updated shirt name". Which entry in the array is that string supposed to be applied to? Similarly, how would that information be passed to the server for it to construct the appropriate update.
It is absolutely possible to update documents in an array, here is some documentation about that specifically. But the actual structure of the command depends on the logic that you are attempting to provide from the application itself.
The only other thing that comes to mind here is that the motivation for the schema described in the question seems a little unclear. Why is the shirt field defined as an array here? Perhaps it should instead just be an embedded document. If so then the mechanics of updating the field in the subdocument are more straightforward and none of the aforementioned concerns about updating arrays remain relevant.
just make an update api where you just have to pass the id and and pass the shirt in the findByIdAndUpdate query and hit the postman by passing the below code.
shirt: [
{
name: "jhrh",
image: String,
},
],

Can I populate more fields after I have already l loaded a document on mongoose?

I want to populate aditional fields after I have already loaded one document.
I am loading my cart on a ecommerce I'm building, like this on all routes:
app.use(function(req, res, next) {
Cart.findOne({session: req.cookies['express:sess']})
.populate({ path: "products.product", select: "price name photos slug" })
.exec(function(err, cart){
if(err){
return err; //TODO: PAG 500
}
if(cart){
res.locals.cart = cart;
} else {
res.locals.cart = new Cart({ session: req.cookies['express:sess']});
}
next();
});
});
But at one page, I'd like to have more the fields description and addons from product loaded.
I tried to just load the products, but then I miss the associated information of quantity that I have on the cart
var CartSchema = new Schema({
products: [{
product: { type: Schema.ObjectId, ref : 'Product' },
quantity: { type: Number, default: 1}
}],
totalItems: { type: Number, default: 0},
message: { type: String },
});
I know I could break this up in more middlewares, according to my needs on fields on different pages, or reload the cart, and I could also just go through both arrays, the products I reload and the products I loaded on the cart and do some kind of merging, but I figured that mongoose might have some way to do this.
This can actually be done:
https://mongoosejs.com/docs/api.html#document_Document-populate
So in this specific case, I'd need to add this piece of code to the function that wants cart with more fields populated, and the middleware wouldn't need any changes
ES5 with callback:
var populate = [
{ path: "products.product", select: "price name photos slug" },
{ path: "card", select: "price name photo"}
];
var cart = res.locals.cart;
cart.populate(populate, function(err, populatedCart) {
res.locals.cart = populatedCart;
next();
});
With ES6:
const populate = [
{ path: "products.product", select: "price name photos slug" },
{ path: "card", select: "price name photo"}
];
res.locals.cart = await res.locals.cart.populate(populate).execPopulate();
You cannot "re-populate" a populated field.
How about a simple if to determine which fields you want to populated. For example:
app.use(function(req, res, next) {
var productSelect;
// This is just an example, you can get the condition from params, body, header..
if (req.body.isMoreField) {
productSelect = 'add more field in here';
}
else {
productSelect = 'less field here';
}
Cart
.findOne({
// ...
})
.populate({
// ...
select: productSelect,
// ...
})
.exec()
.then(function(cart) {
// ...
})
});

Mongoose searching FindOne with multiple arguments

My first attempt at building something with Angular + express + mongodb, so I'm probably going about this completely the wrong way. Express is being used to serve up json. Angular then takes care of all the views etc.
I'm using Mongoose to interact with Mongo.
I have the following database schema:
var categorySchema = new mongoose.Schema({
title: String, // this is the Category title
retailers : [
{
title: String, // this is the retailer title
data: { // this is the retailers Data
strapLine: String,
img: String , // this is the retailer's image
intro: String,
website: String,
address: String,
tel: String,
email: String
}
}
]
});
var Category = mongoose.model('Category', categorySchema);
and in Express I have a couple of routes to get the data:
app.get('/data/categories', function(req, res) {
// Find all Categories.
Category.find(function(err, data) {
if (err) return console.error(err);
res.json(data)
});
});
// return a list of retailers belonging to the category
app.get('/data/retailer_list/:category', function(req, res) {
//pass in the category param (the unique ID), and use that to do our retailer lookup
Category.findOne({ _id: req.params.category }, function(err, data) {
if (err) return console.error(err);
res.json(data)
});
});
The above works - I'm just having big problems trying to get at a single retailer. I'm passing the category, and retailer id through... I've tried all sorts of things - from doing a find on the category, then a findOne on the contents within... but I just cant get it to work. I'm probably going about this all wrong...
I found this thread here: findOne Subdocument in Mongoose and implemented the solution - however, it returns all my retailers - and not just the one I want.
// Returns a single retailer
app.get('/data/retailer_detail/:category/:id', function(req, res) {
//pass in the category param (the unique ID), and use that to do our retailer lookup
Category.findOne({_id: req.params.category , 'retailers.$': 1}, function(err, data) {
console.log(data);
if (err) return console.error(err);
res.json(data)
});
});
Thanks,
Rob
Now that I see your full filter/query, you should be able to use the array positional operator in this case as part of the projection rather than doing client side filtering:
app.get('/data/retailer_detail/:category/:id', function(req, res) {
//pass in the category param (the unique ID), and use that to do our retailer lookup
Category.findOne({
/* query */
_id: req.params.category ,
'retailers._id' : req.params.id
},
{ /* projection */
"retailers.$" : 1
},
function(err, data) {
var retailer = _.where(data.retailers , { id : req.params.id });
if (err) return console.error(err);
res.json(retailer)
});
});
For the { "retailers.$" : 1 } to work properly, the query must include a field from an element in the array. The $ operator returns the first match only.
The guys next door use Mongo + Express and gave me some pointers: they explained to me how mongo worked, and advised I should use underscore.js to assist with my filter.
They said I needed to pull the entire category out - and then run the filter. I don't strictly need , 'retailers._id' : req.params.id} but they said to leave it in as it guaranteed that the category would only be returned if an item within it contained that information. I still don't really know why or how... So can't really mark this as solved.. it it solved, but I don't really get why as yet - so will do more reading :)
app.get('/data/retailer_detail/:category/:id', function(req, res) {
//pass in the category param (the unique ID), and use that to do our retailer lookup
Category.findOne({_id: req.params.category , 'retailers._id' : req.params.id}, function(err, data) {
var retailer = _.where(data.retailers , { id : req.params.id });
if (err) return console.error(err);
res.json(retailer)
});
});

Resources