create mongoose schema with array of objects - node.js

Even though the question have been asked numerous time none of the answers have any idea to help me .
This is my mongoose Schema
const mongoose = require('mongoose')
const { Schema } = mongoose;
const recipeSchema = new Schema({
name: { type: String, required: true },
description: { type: String, required: true },
imagePath: { type: String, required: true },
ingredients:[
{
name:{type:String, required:true},
amount:{type:Number,required:true }
}
]
})
module.exports = mongoose.model("Recipe",recipeSchema);
what i need is to get the data from angular and store it to my database using node
const Recipe = require('../models/recipe.model');
const recipeCtrl={};
recipeCtrl.CreateRecipeServer =async(req, res, next)=>{
if(!req.file) {
return res.status(500).send({ message: 'Upload fail'});
}
else {
let ingredientArray=new Array()
ingredientArray.push(req.body.ingredients)
req.body.imageUrl = 'http://192.168.0.7:3000/images/' + req.file.filename;
const recipe=new Recipe({
name:req.body.name,
description:req.body.description,
imagePath:req.body.imageUrl,
ingredients:[
{
name:ingredientArray,
amount:ingredientArray }
]
});
await recipe.save();
}
Everything except the ingredients array works perfectly/as i require.
I am getting the ingredients as an array from formdata so it have to be JSON.stringfied inorder to append with the form. So what i am getting at backend is string . eg
**[{"name":"dasdasd","amount":2},{"name":"fsfsd","amount":2},{"name":"sdfsdgd","amount":3}]**
this is a string. Any ideas on how to convert it and store to database

use JSON.parse and choose first element of that
JSON.parse(data)[0]

Related

Removing an element from the array in MongoDB through mongoose

I've defined a MongoDB model using mongoose in my NodeJS application as:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const userSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
rToken: [String]
})
module.exports = mongoose.model("User", userSchema);
Every rToken is unique. I want to remove an element from the rToken array specified by the user in the request body, therefore, I've created a route below:
module.exports.logout = async (req, res) => {
const token = req.body.token;
User.updateOne({}, {$pull: {$in: [token]}});
res.sendStatus(204);
}
But it isn't removing the element and I'm still getting 204 status. Please help.
If rToken is an object you can remove element like that, assuming you remove a password key:
const { password, ...rTokenWithoutPassword } = rToken;
Then use rTokenWithoutPassword to do anything.
If you want to remove more than one key, salt and password for example:
const { password, salt, ...rTokenWithoutPassword } = rToken;
If you need to remove from within mongoDB, assuming rToken is an array of strings, you need to modify your Mongoose model because actually it is a String, not an array.
Then, according to mongodb documentation: https://www.mongodb.com/docs/manual/reference/operator/update/pull/
Modify your update request, assuming "abcd" is the value to pull off:
User.updateOne({}, { $pull: { rToken: { $eq: 'abcd' } } });
Here is a playground: https://mongoplayground.net/p/Fq-bH7vBUF7

Update multiple documents instance in MongoDb collection with Mongoose. save() is not a function

Mongoose newbe here. I got the following function to update the references (deleting them) in the document Post when a Tag is deleted. When I call my GraphQl API this is what I got:
message": "posts.save is not a function"
The function in my gql resolver:
async deleteTag(root, { id }, context) {
const posts = await Post.find();
const tag = await Tag.findById(id);
if(!tag){
const error = new Error('Tag not found!');
error.code = 404;
throw error;
}
posts?.forEach(async (post) => {
await post.tags.pull(id);
})
await posts.save()
await Tag.findByIdAndRemove(id);
return true;
}
This is the Post model:
const PostSchema = new Schema({
body: {
type: String,
required: true
},
tags: {
type: [Schema.Types.ObjectId],
ref: 'Tag',
required: false
},
});
and this is the Tag model:
const TagSchema = new Schema(
{
name: {
type: String,
required: true
},
},
{ timestamps: true }
);
Looks like I can't call the method save() on the array of objects returned by Exercise.find()
I used the same pattern in other functions, the difference is that there I used .findById()
Any solution? Advice and best practice advide are super welcome.
You have to save the posts individually:
posts?.forEach(async (post) => {
await post.tags.pull(id);
await post.save();
})
Or use Model.updateMany() combined with the $pull operator.
FWIW, you should probably limit the number of matching Post documents by selecting only documents that have the specific tag listed:
await Post.find({ 'tags._id' : id });

Not able to save my array in to mongodb database

I am new in backend development, creating a post API by using node js and MongoDB,
This is my schema model
// restaurantSchema.js
const mongoose = require('mongoose');
const foodSchema = new mongoose.Schema({
categoryName:{
type: String,
required: true
},
dish: [
{
dishName: {
type: String,
required: true
},
dishDescription: {
type: String
},
dishPrice: {
type: Number,
required: true
},
// dishImg: {
// type: String,
// },
dishRating: {
type: String
},
}
]
});
const restaurantSchema = new mongoose.Schema({
restaurantName: {
type: String,
},
restaurantRating:{
type: String
},
restaurantAddress: {
type: String
},
category: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'foodSchema'
}
]
})
const Restaurant = new mongoose.model('RESTAURANT', restaurantSchema);
module.exports = Restaurant;
here is the router code
// auth.js
const express = require('express');
const router = express.Router();
require('../db/conn');
const Restaurant = require('../model/restaurantSchema');
const app = express();
router.post('/post-food', async (req, res) => {
try {
console.log(req.body, "body")
const restaurant = new Restaurant({
restaurantName: req.body.restaurantName,
restaurantRating: req.body.restaurantRating,
restaurantAddress: req.body.restaurantAddress,
category: [
{
categoryName: req.body.categoryName
},
{
dishName: req.body.dishName,
dishDescription: req.body.dishDescription,
dishPrice: req.body.dishPrice,
dishRating: req.body.dishRating
}
]
});
await restaurant.save();
console.log(restaurant, "save")
res.status(201).json({ message: "food added successfully" })
} catch (err) {
console.log(err)
}
})
module.exports = router;
this code is going to the app.js (main file)
I am getting only this data while I comment out the category(array) in the auth.js file otherwise not able to save any data in the database file
[1]: https://i.stack.imgur.com/wKn9p.png
First of all this is a great question with very valid code, nicely done!
So, everything is correct in your code except you should declare your foodSchema as a mongoose model as well, just like you're doing so with this one
const Restaurant = new mongoose.model('RESTAURANT', restaurantSchema);
You don't need to use new, you can just omit it.
So basically next to this add the declaration for your foodSchema it should look something like this:
const Restaurant = mongoose.model('RESTAURANT', restaurantSchema);
const Food = mongoose.model('FOOD', foodSchema)
and for exporting them you could do something like this:
module.exports = {
Restaurant: Restaurant,
Food: Food
}
For importing any of these two in your router file you could use destructuring:
const { Restaurant } = require('../model/restaurantSchema')
I hope this helps, have a good day!
You have declared category as a list of mongoose typeId and inserting a list of object of type foodSchema. In your schema file after declaring foodSchema, create a model using that food schema.
const Food = mongoose.model('FOOD', foodSchema)
and now in restaurant schema the category will refer ref: 'Food'
mongoose expecting category as ["61407ce0b6c1fc83d896002e"] but you are providing
[{
categoryName:{
type: String,
required: true
},
dish:[...]
}]
export both Food and Restaurant from the schema file.
module.exports = Food;
for creating restaurant first create an Food
const food = new Food({
categoryName: req.body.categoryName,
dish: [ {
dishName: req.body.dishName,
dishDescription: req.body.dishDescription,
dishPrice: req.body.dishPrice,
dishRating: req.body.dishRating
} ]
})
let foodInserted = await food.save();
then the foodInserted._id to the category of restaurant.
const restaurant = new Restaurant({
restaurantName: req.body.restaurantName,
restaurantRating: req.body.restaurantRating,
restaurantAddress: req.body.restaurantAddress,
category: [foodInserted._id]
});
Follow this documentation

Returning a field which is not in the model

I have projects which store batches of records. When retrieving a project from my mongoDB using mongoose, I want to calculate the number of batches which belong to that specific project.
My Project model-schema currently looks like so:
const schema = new Schema({
name: {
type: String,
required: true,
unique: true
},
slug: {
type: String,
required: true,
unique: true
}
}, {
timestamps: true
})
My batch model-schema looks:
const schema = new Schema({
project:{
type: ObjectId,
ref: 'Project',
required: true
},
file: {
type: String,
required: true
},
orginal_name: {
type: String,
required: true
}
}, {
timestamps: true
})
I have a function which counts the number of batches that belong to a project based off of the batch.project field and then adds to it to the JSON object of the project (e.g project.batchCount). However I have run into a problem where the new field project.batchCount is being removed by the by the models toJSON or toObject function because the field is not present in the model-schema.
My current solution is to add it to the model-schema as a 'dummy field' which is never saved to the mongoDB:
const schema = new Schema({
name: {
type: String,
required: true,
unique: true
},
slug: {
type: String,
required: true,
unique: true
},
batchCount: {
type: Number
}
}, {
timestamps: true
})
However I do not like this way as it makes my model larger than it needs to be and slightly less 'readable'.
Is there a better way to do this? And if so how?
I think what you're looking for is a virtual on the schema. A virtual on a document in mongoose is available locally, but doesn't get saved to the DB.
I liked the idea of using a static on the schema to get the project counts. I also liked the idea of just calling the method on the document rather than the Model, so I implemented it in an instance method. A static would have worked just as well. It depends on your preference.
Here is what I came up with:
batch.js
'use strict';
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const batchSchema = new Schema({
project: {
type: Schema.Types.ObjectId,
ref: 'Project',
required: true
},
file: {
type: String,
required: true
},
original_name: {
type: String,
required: true
}
}, {
timestamps: true
});
batchSchema.virtual('projectCount')
.get(function () {
return this._projectCount;
})
.set(function (v) {
this._projectCount = v;
});
batchSchema.methods.count = async function () {
let found = await this.model('Batch').count({ project: this.project });
this._projectCount = found;
};
const Batch = mongoose.model('Batch', batchSchema);
module.exports = Batch;
The virtual called projectCount has a simple setter and getter to overwrite the value if you need to or retrieve it once it's been set.
The instance method on each document is called count() and calls the Model.count() method with a query for the current document's project _id.
project.js
'use strict';
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const projectSchema = new Schema({
name: {
type: String,
required: true,
unique: true
},
slug: {
type: String,
required: true,
unique: true
}
}, {
timestamps: true
});
const Project = mongoose.model('Project', projectSchema);
module.exports = Project;
populate.js
#!/usr/bin/env node
'use strict';
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
const conn = mongoose.connection;
const Batch = require('./batch');
const Project = require('./project');
const projects = [];
const batches = [];
for (let i = 0; i < 10; i++) {
const project = new Project({
name: `project${i}`,
slug: `slug${i}`
});
for (let j = 0; j < 10; j++) {
const batch = new Batch({
project: project._id,
file: `file${j}`,
original_name: `name${j}`
});
batches.push(batch);
}
projects.push(project);
}
async function add () {
await conn.dropDatabase();
const savedProjects = await Project.create(projects);
const savedBatches = await Batch.create(batches);
console.log(`Added ${savedProjects.length} projects.`);
console.log(`Added ${savedBatches.length} batches.`);
return conn.close();
}
add();
populate.js is just how I created the collections and the docs for this example, nothing fancy here.
get.js
#!/usr/bin/env node
'use strict';
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
const conn = mongoose.connection;
const Batch = require('./batch');
async function get () {
let f = await Batch.findOne({});
await f.count();
console.log(`the project for this batch has ${f.projectCount} matches`);
return conn.close();
}
get().catch(console.error);
Once the instance method count() is called, we have access to the value stored in the virtual projectCount.
bash output
49729301: ./populate.js
Added 10 projects.
Added 100 batches.
49729301: ./get.js
the project for this batch has 10 matches
49729301:
mongo shell output
49729301: mongo --quiet test
> db.batches.findOne()
{
"_id" : ObjectId("5acbbebb4cd320cb4e403e8f"),
"project" : ObjectId("5acbbebb4cd320cb4e403e8e"),
"file" : "file0",
"original_name" : "name0",
"createdAt" : ISODate("2018-04-09T19:27:55.395Z"),
"updatedAt" : ISODate("2018-04-09T19:27:55.395Z"),
"__v" : 0
}
>
as you can see, virtual properties are only available locally, and do not get stored in the db.

How to set ObjectId as a data type in mongoose

Using node.js, mongodb on mongoHQ and mongoose. I'm setting a schema for Categories. I would like to use the document ObjectId as my categoryId.
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Schema_Category = new Schema({
categoryId : ObjectId,
title : String,
sortIndex : String
});
I then run
var Category = mongoose.model('Schema_Category');
var category = new Category();
category.title = "Bicycles";
category.sortIndex = "3";
category.save(function(err) {
if (err) { throw err; }
console.log('saved');
mongoose.disconnect();
});
Notice that I don't provide a value for categoryId. I assumed mongoose will use the schema to generate it but the document has the usual "_id" and not "categoryId". What am I doing wrong?
Unlike traditional RBDMs, mongoDB doesn't allow you to define any random field as the primary key, the _id field MUST exist for all standard documents.
For this reason, it doesn't make sense to create a separate uuid field.
In mongoose, the ObjectId type is used not to create a new uuid, rather it is mostly used to reference other documents.
Here is an example:
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Schema_Product = new Schema({
categoryId : ObjectId, // a product references a category _id with type ObjectId
title : String,
price : Number
});
As you can see, it wouldn't make much sense to populate categoryId with a ObjectId.
However, if you do want a nicely named uuid field, mongoose provides virtual properties that allow you to proxy (reference) a field.
Check it out:
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Schema_Category = new Schema({
title : String,
sortIndex : String
});
Schema_Category.virtual('categoryId').get(function() {
return this._id;
});
So now, whenever you call category.categoryId, mongoose just returns the _id instead.
You can also create a "set" method so that you can set virtual properties, check out this link
for more info
I was looking for a different answer for the question title, so maybe other people will be too.
To set type as an ObjectId (so you may reference author as the author of book, for example), you may do like:
const Book = mongoose.model('Book', {
author: {
type: mongoose.Schema.Types.ObjectId, // here you set the author ID
// from the Author colection,
// so you can reference it
required: true
},
title: {
type: String,
required: true
}
});
My solution on using ObjectId
// usermodel.js
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const ObjectId = Schema.Types.ObjectId
let UserSchema = new Schema({
username: {
type: String
},
events: [{
type: ObjectId,
ref: 'Event' // Reference to some EventSchema
}]
})
UserSchema.set('autoIndex', true)
module.exports = mongoose.model('User', UserSchema)
Using mongoose's populate method
// controller.js
const mongoose = require('mongoose')
const User = require('./usermodel.js')
let query = User.findOne({ name: "Person" })
query.exec((err, user) => {
if (err) {
console.log(err)
}
user.events = events
// user.events is now an array of events
})
The solution provided by #dex worked for me. But I want to add something else that also worked for me: Use
let UserSchema = new Schema({
username: {
type: String
},
events: [{
type: ObjectId,
ref: 'Event' // Reference to some EventSchema
}]
})
if what you want to create is an Array reference. But if what you want is an Object reference, which is what I think you might be looking for anyway, remove the brackets from the value prop, like this:
let UserSchema = new Schema({
username: {
type: String
},
events: {
type: ObjectId,
ref: 'Event' // Reference to some EventSchema
}
})
Look at the 2 snippets well. In the second case, the value prop of key events does not have brackets over the object def.
You can directly define the ObjectId
var Schema = new mongoose.Schema({
categoryId : mongoose.Schema.Types.ObjectId,
title : String,
sortIndex : String
})
Note: You need to import the mongoose module
Another possible way is to transform your _id to something you like.
Here's an example with a Page-Document that I implemented for a project:
interface PageAttrs {
label: string
// ...
}
const pageSchema = new mongoose.Schema<PageDoc>(
{
label: {
type: String,
required: true
}
// ...
},
{
toJSON: {
transform(doc, ret) {
// modify ret directly
ret.id = ret._id
delete ret._id
}
}
}
)
pageSchema.statics.build = (attrs: PageAttrs) => {
return new Page({
label: attrs.label,
// ...
})
}
const Page = mongoose.model<PageDoc, PageModel>('Page', pageSchema)
Now you can directly access the property 'id', e.g. in a unit test like so:
it('implements optimistic concurrency', async () => {
const page = Page.build({
label: 'Root Page'
// ...
})
await page.save()
const firstInstance = await Page.findById(page.id)
const secondInstance = await Page.findById(page.id)
firstInstance!.set({ label: 'Main Page' })
secondInstance!.set({ label: 'Home Page' })
await firstInstance!.save()
try {
await secondInstance!.save()
} catch (err) {
console.error('Error:', err)
return
}
throw new Error('Should not reach this point')
})

Resources