NodeJs - how to populate the model view with data from another model - javascript

I am practicing Node by making a simple inventory app, in which I have 3 models: Item, Category and Manufacturer. What I want to do is, when displaying an item details, to include the Category and Manufacturer associated with that specific item, however it seems that I can not make it work. Any help would be appreciated.
Here is the Item controller
const Item = require("../models/item");
const Category = require("../models/category");
const Manufacturer = require("../models/manufacturer");
const async = require("async");
exports.index = function (req, res) {
res.render("index", { title: "StrinGuist" });
};
//display list of all items
exports.item_list = function (req, res, next) {
Item.find({}, "name description category in_stock price manufacturer")
.populate("item")
.exec(function (err, list_items) {
if (err) {
return next(err);
}
res.render("item_list", { title: "All items", item_list: list_items });
});
};
//display detail page for a specific item
exports.item_detail = function (req, res, next) {
async.parallel(
{
item: function (callback) {
Item.findById(req.params.id)
.populate("category")
.populate("manufacturer")
.exec(callback);
},
},
function (err, results) {
if (err) {
return next(err);
}
if (results.item == null) {
// No results.
const err = new Error("Item not found");
err.status = 404;
return next(err);
}
// Successful, so render.
res.render("item_detail", {
title: results.item.name,
item: results.item,
});
}
);
};
and here is the view (pug)
extends layout
block content
h1 #{item.name}
div(class='item-detail-content')
p #[strong Price: $] #{item.price}
p #[strong Description: ] #{item.description}
p #[strong In stock: ] #{item.in_stock}
p #[strong Manufacturer: ] #{item.manufacturer.name}
p #[strong Category: ] #{item.category.name}
This throws me an error "Cannot read property 'name' of null", but I am not quite sure what is wrong. I tried also to declare Category and Manufacturer name as 'virtual', but that didn't work either.

For a ORM like mongoose when using mongodb, you can use the populate method with allows you do that.
https://mongoosejs.com/docs/populate.html

Related

How to Update a specific Item in an array nested in a document

Hi I am building an RESTful API in Node using mongoose to manage data on a practice food delivery site I am building.
I want to setup a patch route that will remove an order Item from my items array nested in my Orders document based on a request from the user identifying the specific item with a name or ID.
I have a patch route which pushes a new order item into the Items Array nested in the Orders document, I want this patch route to also be able to remove a specific Item from the array based on a prop such as name or ID
I have tried using the Update and UpdateOne methods and I think I'm just getting the syntax wrong or something as I keep getting errors.
Server.js:
require("dotenv").config()
const express = require("express");
const mongoose = require("mongoose");
const app = express();
mongoose.connect(process.env.DATABASE_URL)
const db = mongoose.connection
db.on("error", () => console.error(error))
db.once("open", () => console.log("connected to database"))
app.use(express.json())
const subscribersRouter = require("./routes/subscribers")
const suscribersLoginRouter = require ("./routes/login")
const restaurantsRouter = require("./routes/restaurants")
const ordersRouter = require("./routes/orders")
app.use("/subscribers", subscribersRouter)
app.use("/login", suscribersLoginRouter)
app.use("/restaurants", restaurantsRouter)
app.use("/orders", ordersRouter)
app.listen(3000, () => {
console.log("Server has started on port 3000")
});
Order Model:
const mongoose = require("mongoose")
const orderSchema = new mongoose.Schema({
userID: {
type: String,
required: true
},
total: {
type: Number,
required: true
},
items: {
type: Array,
required: true
}
})
module.exports = mongoose.model("order", orderSchema)
Orders Route (you will see here that I have a patch route which pushes a new order item into the Items Array nested in the Orders document, I want this patch route to also be able to remove a specific Item from the array based on a prop such as name or ID, the issue I am have is 1. How to create an if statement that gets the update of the item to be triggered and the the code id use in that if statement to actually update that Item)
const express = require("express")
const router = express.Router()
const Order = require("../models/order")
// Getting All
router.get("/", async (req, res) => {
try {
const orders = await Order.find()
res.json(orders)
} catch (err) {
res.status(500).json({
message: err.message
})
}
})
// Getting One
router.get("/:id", getOrder, (req, res) => {
res.json(res.order)
})
// Creating One
router.post("/", async (req, res) => {
const order = new Order({
userID: req.body.userID,
total: req.body.total,
items: req.body.items
})
try {
console.log(order)
const newOrder = await order.save()
res.status(201).json(newOrder)
} catch (err) {
res.status(400).json({
message: err.message
})
}
})
// Updating One
router.patch("/:id", getOrder, async (req, res) => {
if (req.body.userID != null) {
res.order.userID = req.body.userID
}
if (req.body.total != null) {
res.order.total = req.body.total
}
if (req.body.items != null) {
const currentItems = res.order.items
const newItem = req.body.items
currentItems.push(newItem)
}
try {
const updatedItems = await res.order.save()
res.json(updatedItems)
} catch (err) {
res.status(400).json({
message: err.message
})
}
})
// Deleting One
router.delete("/:id", getOrder, async (req, res) => {
try {
await res.order.remove()
res.json({
message: "Deleted Order"
})
} catch (err) {
res.status(500).json({
message: err.message
})
}
})
async function getOrder(req, res, next) {
let order
try {
order = await Order.findById(req.params.id)
if (order === null) {
return res.status(404).json({
message: "Cannot Find Order"
})
}
} catch (err) {
return res.status(500).json({
message: err.message
})
}
res.order = order
next()
}
module.exports = router
TEST Requests:
# ORDERS
# Get All
GET http://localhost:3000/orders
###
#Get One
GET http://localhost:3000/orders/627fe8e575a8229d0ae81e73
###
#Create One
POST http://localhost:3000/orders
Content-Type: application/json
{
"userID": "627f8b476fa64425928750c9",
"total":50,
"items": [
{
"name": "Burder",
"price": "R20",
"description": "A good Fuggen Waffel"
},
{
"name": "Hotdog",
"price": "R20",
"description": "A good Fuggen Waffel"
},
{
"name": "Bunny Chow",
"price": "R20",
"description": "A good Fuggen Waffel"
},
{
"name": "Pizza",
"price": "R20",
"description": "A good Fuggen Waffel"
}
]
}
###
#Delete One or all
DELETE http://localhost:3000/orders/628202c3b208aebc7f7f8f98
###
# Update on (add Order Item)
PATCH http://localhost:3000/orders/628202c3b208aebc7f7f8f98
Content-Type: application/json
{
"items": {
"name": "gravy",
"price": "R20",
"description": "A good Fuggen Waffel"
}
}
###
I'm not sure I understood you correctly. I understood that you need the PATCH route to also delete an item from the items array by name.
So here is my solution to it:
Because you already fetched the order and you just want to delete a specific item from the items property, you can use filter to do so before saving the order document.
res.order.items = res.order.items.filter(({ name }) => name !== itemNameToRemove);
Like this:
// Updating One
router.patch("/:id", getOrder, async(req, res) => {
const {
userID,
total,
items,
itemNameToRemove
} = req.body;
if (userID != null) {
res.order.userID = userID;
}
if (total != null) {
res.order.total = total;
}
if (items != null) {
const newItem = items;
res.order.items.push(newItem);
if (itemNameToRemove) {
res.order.items = res.order.items.filter(({
name
}) => name !== itemNameToRemove);
}
}
try {
const updatedItems = await res.order.save()
res.json(updatedItems)
} catch (err) {
res.status(400).json({
message: err.message
})
}
})
you can use $pull for this.
Order.update(
{ userID : "userID123" },
{$pull : {"items" : {"name":"gravy"}}}
)
This will delete the object with name as gravy belong to the userID : userID123

Post Multiple items / an array with Express & Postman

unfortunately I have problems to post an Array to my Express Server.
I post an item and if it exists in the db it will be deleted.
My code:
item.routes.js
module.exports = app => {
const item = require("../controllers/item.controller.js");
app.post("/itemr", item.IsReserved);
};
item.controller.js
const Item = require("../models/item.model.js");
exports.IsReserved = (req, res) => {
// Validate request
if (!req.body) {
res.status(400).send({
message: "Content can not be empty!"
});
}
const item = new Item({
itemId: req.body.assetId,
botId: req.body.botId,
});
Item.IsReserved(item, (err, data) => {
if (err)
res.status(500).send({
message:
err.message || "Some error occurred while creating the Item(db)."
});
else res.send(data);
});
};
item.model.js
const sql = require("./db.js");
Item.IsReserved = (RItem, result) => {
sql.query(`SELECT * FROM ReservedItems WHERE itemId = "${RItem.itemId}"`, (err, res) => {
if(res.length){
sql.query(`DELETE FROM ReservedItems WHERE itemId = "${RItem.itemId}"`, (err, res) => {
if (err) {
console.log("error: ", err);
result(err, null);
return;
}
result(null, { id: res.insertId, ...RItem });
});
}
else{
result(null, { message: "nothing to do"});
}
});
};
module.exports = Item;
A single item works great with a post like this:
{
"assetId" : 3,
"botId" : "1"
}
but if I want to insert multiple items like this:
[
{
"assetId" : 3,
"botId" : "1"
},
{
"assetId" : 4,
"botId" : "1"
}
]
The result from this is "nothing to do"...
My sql log shows: Query SELECT * FROM ReservedItems WHERE itemId = "undefined"
I saw another post here which had a similar problem, but I don't know how to implement something like ".create()" into my code to recognize multiple items.
I would be very thankful for every answer!
There are many approaches to fix your problem :)
First of all in your controller, this is not possible anymore as you send an array of items in your request payload. So you need to define an array aswell :
const items = req.body.map((item) => new Item({
itemId: item.assetId,
botId: item.botId,
});
As your method IsReserved handles only 1 item, you can use Promise.all to apply modifications. Something like this :
Promise.all( items.map( (item) => Item.isReserved(item)))
.then( (result) => ...do something here)
.catch( (error) => ...do something else here)
Another approach could be to change the SQL statement and use WHERE IN
SELECT * FROM ReservedItems WHERE itemId IN (itemid1, itemid2...)
This will get you an array of items that you can then delete the same way. It seems better in terms of performance.
I also recommand to use async/await syntax as it's way easier to read code containing promises

$addToSet to an array but it gives me null

so basically I've a wish list and I've bunch of products that I want to add inside the the wish list products array using a put request (I'm using postman btw).
This is the wish list schema, and yes I know that the document's name in the db is "whishlist"....I hate typos
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = mongoose.Schema.Types.ObjectId;
var whishList = new Schema({
title: {type: String, default: "Cool whishlist"},
products:[{type: ObjectId, ref:'Product'}]
});
module.exports = mongoose.model('WhishList', whishList);
This is the products schema
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var product = new Schema({
title: String,
price: Number,
likes: {type: Number, default: 0}
});
module.exports = mongoose.model('Product', product);
and now this is the code that I am trying to run
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var db = mongoose.connect('mongodb://localhost/swag-shop');
var Product = require('./model/product');
var wishList = require('./model/wishlist');
app.put('/wishlist/product/add', function(request, response){
Product.find({_id: request.body.productId}, function(err, product){
if(err) {
response.status(500).send({err: "could not add item to wishlist"});
}else{
wishList.update({_id: request.body.wishlistId},{$addToSet: {products: product._id}}, function(err, wishlist){
if(err){
response.status(500).send({err: "could not add item to wishlist /update/"});
}else{
response.send(wishlist);
}
});
}
});
I really can't see where is the problem I tried deleting the document and posting it again but I had the same problem.
Thanks in advance
The issue is that the result from Product.find() is an array of Mongoose documents if the query matches any documents in the collection instead of a single document which you want.
Thus the expression {$addToSet: {products: product._id}} resolves to {$addToSet: {products: undefined}} because product is an array and product._id is undefined. Take this simple example
var product = [{ '_id': 1 }];
console.log(product._id) // logs undefined
To remedy this problem, you can either access the only element in the array as
wishList.update(
{ '_id': request.body.wishlistId },
{ '$addToSet': { 'products': product[0]._id} },
function(err, wishlist) { ... }
);
Or use the findOne() method which returns a single document when querying the product:
Product.findOne({ '_id': request.body.productId }, function(err, product) {
if(err) {
response.status(500).send({err: "could not add item to wishlist"});
} else {
wishList.update(
{ '_id': request.body.wishlistId },
{ '$addToSet': { 'products': product._id } },
function(err, wishlist) { ... }
);
}
});
The findById() method is also useful in this case i.e.
Product.findById(request.body.productId, function(err, product) {
if(err) {
response.status(500).send({err: "could not add item to wishlist"});
} else {
wishList.update(
{ '_id': request.body.wishlistId },
{ '$addToSet': { 'products': product._id } },
function(err, wishlist) { ... }
);
}
});

How to add object to nested Array using Node.js and Mongoose

How can I add object to my nested Array in PartnerSchema?
I separate documents, because in the future there will be more of nested arrays.
This is my schema:
var productSchema = new mongoose.Schema({
name: String
});
var partnerSchema = new mongoose.Schema({
name: String,
products: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Product'
}]
});
module.exports = {
Partner: mongoose.model('Partner', partnerSchema),
Product: mongoose.model('Product', productSchema)
}
And this is my backend:
var campSchema = require('../model/camp-schema');
router.post('/addPartner', function (req, res) {
new campSchema.Partner({ name : req.body.name }).save(function (err, response) {
if (err) console.log(err);
res.json(response);
});
});
router.post('/addProduct', function (req, res) {
campSchema.Partner.findByIdAndUpdate({ _id: req.body.partnerId },
{
$push: {
"products": {
name: req.body.dataProduct.name
}
}
}, { safe: true }, function (err, response) {
if (err) throw err;
res.json(response);
});
});
I can add Partner by using /addPartner and it works fine.
Problem is with second function /addProduct I can't add Product to Array in Partner Schema. I have an error: CastError: Cast to undefinded failed for value "[object Object]" at path "products"
Since the products field in Partner model is an array that holds _id refs to the Product model, you are supposed to push an _id to the array, not an object hence Mongoose complains with an error.
You should restructure your code to allow the saving of the Product _id ref to the Partner model:
router.post('/addProduct', function (req, res) {
var product = new campSchema.Product(req.body.dataProduct);
product.save(function (err) {
if (err) return throw err;
campSchema.Partner.findByIdAndUpdate(
req.body.partnerId,
{ "$push": { "products": product._id } },
{ "new": true },
function (err, partner) {
if (err) throw err;
res.json(partner);
}
);
});
});

Sails.js: Inserting one-to-many associated models to MongoDB

I am trying to take advantage of the Waterline ORM in Sails.js to build an example app that has a model called 'Category'. Because a category can have multiple sub categories, I have the following one-to-many association for this model:
module.exports = {
adapter: 'mongo',
// adapter: 'someMysqlServer',
attributes: {
categoryTitle: {
type: 'string',
required: true
},
parentCat: {
model: 'category'
},
subCategories: {
collection: 'category',
via: 'parentCat'
},
articles: {
collection: 'article',
via: 'category',
required: false
}
}
};
In the CategoryController.js, I have the create method that first tries to see if the new category has a parent category assigned to it; however, I feel the code is quite messy, and the parentCat in Mongodb is always empty even if I tried to assign a parent category in the form submission. So I am wondering if this is the right way to do it:
create: function(req, res, next) {
var params = req.allParams();
// set parent category if exists
if (params.parentCat) {
Category.findOne({categoryTitle : params.parentCat})
.exec(function(err, category) {
if (err) {
return false; //not found
} else {
params.parentCat = category.id; //found the parent category
console.log('parent cat id is: ', category.id);
}
});
}
Category.create(params, function(err, newCategory) {
if (err) {
return next(err);
} else {
console.log('new category created');
}
console.log('successfully added the category: ' + newCategory.categoryTitle)
res.redirect('/category');
}); // create the category
}
The issue of your code is the callback.
I created a new version of code with the async feature (which is already in your sails app), hope it will help you.
create: function(req, res, next) {
var params = req.allParams();
async.waterfall([
function(callback) {
// set parent category if exists
if (params.parentCat) {
Category.findOne({
categoryTitle: params.parentCat
})
.exec(function(err, category) {
if (err) {
return false; //not found
}
params.parentCat = category.id; //found the parent category
console.log('parent cat id is: ', category.id);
callback(null, params);
});
} else {
callback(null, params);
}
},
function(params, callback) {
Category.create(params, function(err, newCategory) {
if (err) {
return next(err);
}
console.log('successfully added the category: ' + newCategory.categoryTitle);
callback(null, newCategory);
}); // create the category
}
], function(err, result) {
console.dir(result);
res.redirect('/category');
});
}

Categories