How to save a refrence between 2 collections in MongoDB? - node.js

I'm developing an app using Node.js, Mongoose, MongoDb, express.
I have 2 schemas one for student and one for snippets. I'm using the population model population model. I can create a user, and create a snippet and link it to the user. But I can't link and save the snippets in the user collection.
How to link and save the user so that it can have a reference to his snippets?
user and snippet schema
var userSchema = Schema({
name: { type: String, required: true, unique: true },
password: { type: String, required: true },
snippet: [{ type: Schema.Types.ObjectId, ref: 'Snippet' }]
})
var snippetSchema = Schema({
user: {type: Schema.Types.ObjectId, ref: 'User'},
title: String,
body: String,
createdAt: {
type: Date,
require: true,
default: Date.now
}
})
This is how I save the user which usually when the user register.
var username = request.body.name
var password = request.body.password
var newUser = new User({
name: username,
password: password
})
newUser.save().then(function () {
console.log('success')
response.redirect('/')
})
This is how I save the snippets I add it inside a user .save() function so that it saves the snippet ref but it gives me name.save() is not a function error.
var name = request.session.name.name
name.save().then(function () {
// Using a promise in this case
var newSnippet = new Snippet({
user: name._id,
title: title,
body: snippet
})
newSnippet.save().then(function () {
// Successful
console.log('success')
response.redirect('/')
})
}).catch(function (error) {
console.log(error.message)
response.redirect('/')
})

name.save() is not a function because name is just a string in this case, not a Mongoose object.
One way to do it is to create your user, then retrieve it and add a snippet:
var name = request.session.name.name
User.find({ name: name }).then(function (user) {
// Here you get your user by name
var newSnippet = new Snippet({
user: user._id, // And here you can pass user _id
title: 'your title',
body: 'your snippet'
})
newSnippet.save()
})

Related

Mongoose pre hook findOneAndUpdate to modify document before saving

I am using the mongoose pre hook for findOneAndUpdate. I went through the documentation to understand better it's usage. I would like to update the password field before it saves to DB. However, I am not getting the disired result - nothing gets changed. What would be the right approach for using the findOneAndUpdate pre hook to modify a certain field in the doc?
Actual Document
{
_id: new ObjectId("622457f5555562da89b7a1dd"),
id: '5982ca552aeb2b12344eb6cd',
name: 'Test User',
configuration: [
{
email: 'test2#gmail.com',
password: 'p#ssw0rd',
_id: new ObjectId("9473l58f2ad34efb816963dd"),
},
{
email: 'test3#gmail.com',
password: 'trUstN0oNe',
_id: new ObjectId("8674884cec1877c59c8838e0")
}
],
__v: 0
}
Desired Document
{
_id: new ObjectId("622457f5555562da89b7a1dd"),
id: '5982ca552aeb2b12344eb6cd',
name: 'Test User',
configuration: [
{
email: 'test2#gmail.com',
password: '0f359740bd1cda994f8b55330c86d845',
_id: new ObjectId("9473l58f2ad34efb816963dd"),
},
{
email: 'test3#gmail.com',
password: '3dba7872281dfe3900672545356943ce',
_id: new ObjectId("8674884cec1877c59c8838e0")
}
],
__v: 0
}
Code:
const UserSchema = new Schema({
id: {
type: String,
required: [true, "'id' value is required"]
},
name: {
type: String,
required: [true, "'name' value is required"]
},
configuration: [ConfigModel.schema]
});
const ConfigSchema = new Schema({
email: {
type: String,
required: [true, "Email is required"]
},
password: {
type: String,
required: [true, "Password is required"]
}
});
UserSchema.pre('findOneAndUpdate', async function(next) {
const docToUpdate = await this.model.findOne(this.getQuery());
docToUpdate.configuration.forEach((item,i) => {
docToUpdate.configuration[i].password = md5(item.password);
});
return next();
});
You are missing the .save() document command after changing the information inside the document, because you are only using findOne
const docToUpdate = await this.model.findOne(this.getQuery());
docToUpdate.botconfiguration.forEach((item,i) => {
docToUpdate.configuration[i].password = md5(item.password);
});
await docToUpdate.save() // <---- this line
You dont need the updateMany() here because the ConfigSchema is nested inside the user collection
in userModel you read configuration from ConfigModel so you have to modify the config model not user model it just read and populate the data from config model.

MongoDB populate() to dynamically load/migrate data not working

I am building an app in which the user adds and deletes objects (Pic) in an array('pics') after registering, but not sure how to dynamically load or populate('pics') to userSchema to automatically render. The user registers on the app with that array originally empty ('pics' = zero), and will create or delete those objects thereafter when logged in.
Following the documentation, I used "await User.find().populate('pics');" to migrate data in index method, but did not work.
Besides, should I include 'pics' key at store method, or userSchema 'pics' should be enough?
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
pics: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Pic"
}
],
});
const picSchema = new mongoose.Schema({
thumbnail: String,
description: String,
dev: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
},
);
const User = mongoose.model('User', userSchema);
const Pic = mongoose.model('Pic', picSchema)
async index(req, res, next) {
const users = await User.find().populate('pics');
res.status(200).json(
devs
);
},
async store(req, res) {
try {
const { name } = req.body;
let user = await User.create({
name,
pics
})
// await user.populate('pics').execPopulate();
res.send({ user })
}
} catch (error) {
res.status(400).send(error);
}
},
I worked a time ago with MongoDB and NodeJS. I think that you have a problem with the definitions. Also, you can read the documentation https://mongoosejs.com/docs/populate.html
You need to define the _id for collections (Schema).
const userSchema = new mongoose.Schema({
_id: new mongoose.Types.ObjectId(),
name: {
type: String,
required: true,
trim: true
},
pics: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Pic"
}
],
});
const picSchema = new mongoose.Schema({
_id: new mongoose.Types.ObjectId(),
thumbnail: String,
description: String,
dev: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
},
);
So, when you create a new User, the _id is completed (you can generate it or it can be generated automatically) and ignore the field pics. When you create a Pic, you need to read the _id of the User and assigned as 'dev', something like:
let pic = new Pic({
thumbnail: '', description: '',
dev: yourUser._id
});
Using this way to create documents, you can use the populate function.

How do I get the logged-in User ID when creating a post request for that user without using req.params.id?

Let's say I have an API with a /posts endpoint. Each post in my Mongoose schema has a reference to the user that created it, and each user has an array of references to the posts they created.
When posting as a logged-in user, in order to save that reference I need to send the current logged-in user ID along with the content of the post to the /posts endpoint. I would prefer not to do so through some nested query like /users/:id/posts and then send req.params.id. I would like to post directly to /posts but send the user.id in the request somehow.
User model:
const UserSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
posts: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}]
});
Posts model:
const PostSchema = new Schema({
content: {
type: String,
required: true
},
created: {
type: Date,
default: Date.now
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
});
Create new post (need a way to get the user ID since it won't actually be in req.params)
exports.createPost = async function(req, res, next) {
try {
const { content } = req.body; // ideally would get user ID from here
const post = await db.Post.create({
content,
user: req.params.id
});
const postUser = await db.User.findById(req.params.id);
postUser.posts.push(post.id);
await postUser.save();
const newPost = await db.Post.findById(post.id);
const {id, created} = newPost;
return res.status(201).json({
id,
content,
created
})
}
catch(err) {
return next(err);
}
}
I know I'm probably missing something obvious, but I appreciate any suggestions.

How to insert data in to related mongoose Schemas?

I am trying to create an api endpoint in nodejs to insert data in mongodb. I have got two mongoose schemas which are related to each other that`s why i am relating these two schemas like below:
Posts schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const PostSchema = mongoose.Schema({
title: { type: String, trim: true, required: true},
description: { type:String, required: true },
created_at: { type: Date, default: Date.now },
author: {type: Schema.ObjectId, ref: 'Author', required: true},
});
const Post = module.exports = mongoose.model('Post', PostSchema);
Authors Schema:
const mongoose = require('mongoose');
const AuthorSchema = mongoose.Schema({
fullname: { type: String, trim: true, required: true},
address: { type: String, required: true },
phone: { type: Number, required: true },
email: { type: String, required: true },
created_at: { type: Date, default: Date.now }
});
const Author = module.exports = mongoose.model('Author', AuthorSchema);
Now i can easily insert data for authors schema
Authors.js:
router.post('/', (req, res, next) => {
let newAuthor = new Authors({
fullname: req.body.fullname,
address: req.body.address,
phone: req.body.phone,
email: req.body.email
});
newAuthor.save((err, user) => {
if(err) {
res.json({
success: false,
msg: "Failed to add author"
});
} else {
res.json({
success: true,
msg: "Author added successfully"
});
}
});
});
But for posts i am stuck in here
posts.js:
router.post('/', (req, res) => {
var newPost = new Posts({
title: req.body.title,
description: req.body.description,
author:
})
})
main problem is how to get author??
You can set author id in author field.
// you can set authorId in req.body and use that
router.post('/', (req, res) => {
var newPost = new Posts({
title: req.body.title,
description: req.body.description,
author: req.body.authorId
})
});
OR you can set author id in route path and use req.params.authorId
// for that route from ui call should be like
// var userId = 4654654545454
// $http.post('/'+userId)....
router.post('/:authorId', (req, res) => {
var newPost = new Posts({
title: req.body.title,
description: req.body.description,
author: req.params.authorId
})
});
On the page where you allow users to create posts, you need to pass the author's id along with the rest of the post details. Then you can simply refer to the author's id by whatever you chose to send it as (i.e. authorId).
If you are using a serializer that takes all the values from your form and nicely packages them, then insert a hidden input field that stores the author's id, so you can capture that as well. For example:
<input type="hidden" name="authorId" value={user._id} />
Otherwise, if you are manually packaging the form field values, then just add the author's id as another property in the response object. Not sure what you're doing to send the request but say you were using the axios library to send an ajax post to your endpoint you could do this to easily add the author to the response:
const title = document.getElementByNames("title")[0].value
const description = document.getElementByNames("description")[0].value
const author = document.getElementByNames("authorId")[0].value
axios.post("/posts", {title: title, description: description, author: authorId}).then(res => console.log(res))

Manually populating Mongodb field on document creation with Mongoose

Following the Mongoose documentation, I was able to create two docs, but am unable to populate one with the other.
Despite manually setting the 'account' value to reference the other document, my database doesn't seem to create the relation.
Below is the code I've used:
UserAuth.findOne({ email }, (err, user) => {
if (err) return done(err);
if (user) {
return done(null, false,
{ message: 'It appears that email address has already been used to sign up!' });
}
// Create the user account
const newAccount = new UserAccount({
name: {
first: req.body.firstName,
last: req.body.lastName,
},
});
newAccount.save((err) => {
if (err) throw err;
// Create the user authentication
const newAuth = new UserAuth({
email,
account: newAccount,
});
newAuth.password = newAuth.generateHash(password);
newAuth.save((err) => {
if (err) throw err;
return done(null, newAuth);
});
return done(null, newAccount);
});
});
Collections:
User Auth
const UserAuthSchema = new Schema({
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
account: {
type: Schema.Types.ObjectId,
ref: 'User',
},
});
module.exports = mongoose.model('UserAuth', UserAuthSchema);
User Account
const UserSchema = new Schema({
name: {
first: {
type: String,
required: true,
},
last: {
type: String,
required: true,
},
},
team: {
type: Schema.Types.ObjectId,
ref: 'Team',
},
image: {
type: String,
default: 'assets/default_user.png',
},
});
module.exports = mongoose.model('User', UserSchema);
It looks like the part:
// Create the user authentication
const newAuth = new UserAuth({
email,
account: newAccount,
});
should be:
// Create the user authentication
const newAuth = new UserAuth({
email,
account: newAccount._id,
});
And then, when you query the collection, you have to say which field should be populate, as shown in (Mongoose documentation)[http://mongoosejs.com/docs/populate.html]
Ad please check that the types of the 2 linked fields are the same as mentioned in the documentation.

Resources