CastError: Cast to ObjectId failed for value ...` at path "questions" - javascript

I'm currently building a Node backend with MongoDB / Mongoose and I seem to be having some problem with tying my data together. Specifically, I wish for all users to be able to submit a form (question form) which will then be added to the "questions" collection. In addition to being added to the questions collection, I also need to store a reference to all of the questions a user has answer directly inside of the user object.
Below you can check out my code. Whenever I make a POST requestion to /questions, it spits out this error. I should note that it successfully adds documents into the questions collection, and each question contains the ID of the user who created it, but the main problem is the user's questions array is not getting updated to include an ID value of submitted questions.
Models/User.js
const mongoose = require('mongoose'),
Schema = mongoose.Schema,
bcrypt = require('bcrypt-nodejs');
const UserSchema = new Schema({
email: {
type: String,
lowercase: true,
unique: true,
required: true
},
password: {
type: String,
required: true
},
profile: {
firstName: { type: String },
lastName: { type: String }
},
questions: [
{
type: Schema.Types.ObjectId,
ref: 'Question'
}
],
role: {
type: String,
enum: ['Member', 'Client', 'Owner', 'Admin'],
default: 'Member'
},
resetPasswordToken: { type: String },
resetPasswordExpires: { type: Date }
},
{
timestamps: true
});
/** Pre-save of user to database,
hash password if password is modified or new
*/
module.exports = mongoose.model('User', UserSchema);
Models/Question.js
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
// Schema defines how questions will be stored in MongoDB
const QuestionSchema = new Schema({
questionString: String,
answer: Boolean,
_createdBy : [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
],
},{
//user timestamps to save date created as .createdAt
timestamps: true
});
module.exports = mongoose.model('Question', QuestionSchema);
Controller/QuestionController.js
const jwt = require('jsonwebtoken'),
crypto = require('crypto'),
Question = require('../models/question'),
User = require('../models/user'),
config = require('../config/main');
function setQuestionInfo(request) {
return {
_id: request._id,
questionString: request.questionString,
answer: request.answer,
user: request.user
}
}
exports.addQuestion = function(req, res, next) {
User.findById(req.user.id, (err, user) => {
if (err) throw new Error(err);
// We create an object containing the data from our post request
const newQuestion = {
questionString: req.body.questionString,
answer: req.body.answer,
// in the author field we add our current user id as a reference
_createdBy: req.user._id
};
// we create our new post in our database
Question.create(newQuestion, (err, question) => {
if (err) {
res.redirect('/');
throw new Error(err);
}
// we insert our newQuestion in our posts field corresponding to the user we found in our database call
user.questions.push(newQuestion);
// we save our user with our new data (our new post).
user.save((err) => {
return res.send('sucess!');
});
})
});
}
Router.js
module.exports = function(app) {
// Initializing route groups
const apiRoutes = express.Router(),
userRoutes = express.Router(),
authRoutes = express.Router(),
questionRoutes = express.Router();
//=========================
// Auth Routes
//=========================
/** ROUTES BELOW WORK FINE -- ONLY DEALS WITH POST TO /questions
*
app.use middle ware sets /auth as auth route (everything goes through /api/auth)
apiRoutes.use('/auth', authRoutes);
apiRoutes.get('/dashboard', requireAuth, function(req, res) {
res.send('It worked! User id is: ' + req.user._id + '.');
});
// Set user routes as a subgroup/middleware to apiRoutes
apiRoutes.use('/user', userRoutes);
// View user profile route
userRoutes.get('/:userId', requireAuth, UserController.viewProfile);
// Test protected route
apiRoutes.get('/protected', requireAuth, (req, res) => {
res.send({ content: 'The protected test route is functional!' });
});
// Registration route
authRoutes.post('/register', AuthenticationController.register);
// Login route
authRoutes.post('/login', requireLogin, AuthenticationController.login);
*/
// Problem Area --> Making POST req to /questions
apiRoutes.post('/questions', requireAuth, QuestionController.addQuestion);
// Set url for API group routes
app.use('/api', apiRoutes);
};

You've your schema defined to accept question ids for a user.
questions: [
{
type: Schema.Types.ObjectId,
ref: 'Question'
}
After you save with Question.create(newQuestion, (err, question)... the callback attribute question has the updated data, one with the ObjectId.
Now you add this ObjectId value to your existing questions array that you got from findById on User model.
user.questions.push(question._id);
Mongoose will use the questionId to fill your question object when you use populate on questions array, but thats part for retrieving information.

Related

Reference another Schema in Mongoose

so I have to Schemas. PostSchema and UserSchema
const mongoose = require("mongoose")
const PostSchema = new mongoose.Schema({
content: {
type: String,
required: true,
},
likes: {
type: Number,
required: true
},
rescreams: {
type: Number,
required: true
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
createdAt: {
type: Date,
default: Date.now
}
})
module.exports = mongoose.model("Post", PostSchema)
UserSchema:
const bcrypt = require("bcrypt");
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({
userName: { type: String, unique: true },
email: { type: String, unique: true },
password: String,
});
// Password hash middleware.
UserSchema.pre("save", function save(next) {
const user = this;
if (!user.isModified("password")) {
return next();
}
bcrypt.genSalt(10, (err, salt) => {
if (err) {
return next(err);
}
bcrypt.hash(user.password, salt, (err, hash) => {
if (err) {
return next(err);
}
user.password = hash;
next();
});
});
});
// Helper method for validating user's password.
UserSchema.methods.comparePassword = function comparePassword(
candidatePassword,
cb
) {
bcrypt.compare(candidatePassword, this.password, (err, isMatch) => {
cb(err, isMatch);
});
};
module.exports = mongoose.model("User", UserSchema);
My question is: I'm trying to reference the User Object ID in the Post Schema. As you can see, I've done that with type: mongoose.Schema.Types.ObjectID. And I've seen this multiple times. But in my database, the User never shows up in the Document. What do I need to do?
Cheers
There is a difference between referencing a document and embedding a document.
If you want to store a document inside a document you should embed it, thus read operations will be faster because you won't need to perform JOIN operations.
Whereas referencing means storing an ID of an entity that you are referencing, when you need to access the document you are referencing, you need to fetch it from the collection by the ID you have stored. It is slower than embedding, but it gives you higher consistency and data integrity because the data is stored once at the collection and not duplicated at every object. And MongoDB does not support foreign keys so you should be careful with referencing.
So when you are storing the document using ref, you need to put an ObjectID as a user and then fetch the document you need to add populate call. e.g.
PostShema.findOne({ _id: SomeId }).populate('user');
try to save in a variable:
const UserId = UserSchema.Schema.Types.ObjectId;
for more information:
https://mongoosejs.com/docs/api/schema.html#schema_Schema.Types

Reflecting Changes To Mongo DB After New Post Request - Mongoose & Nodejs

I'm sorry, I'm having a hard time even formulating the question properly. Hopefully it's not too confusing.
I'm building a One To Many Relations in my Mongo DB Atlas. I'm using mongoose and Nodejs.
I'm trying to create a One User to Many Entries. For now let's just say it's a one to one, to remove a layer of complexity. One User To One Entry.
All the code in the backend works, but in short the issue I have is that.
Whenever I make a post request to create a new entry, I can include the user ID that the entry belongs to in the request. But whenever I make a post request to create a new user, I can't include an entry ID in the request, because no requests exist yet for that user. When I create a new entry, mongo db doesn't automatically update the document, to add that new entry to the user associated with it. And I don't know what I need to do on my end to get it to dynamically update the users to include new entries that belong to them.
Here are my models/schemas for users and entries, so you can see the association.
USER SCHEMA
const mongoose = require('mongoose');
const userSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
email: {type: String,
required: true,
unique: true,
displayName: String,
password: {type: String, required: true},
entry: {type: mongoose.Schema.Types.ObjectId, ref: 'Entry', required: true}
}, {collection: "users"});
module.exports = mongoose.model("User", userSchema);
ENTRY SCHEMA
const mongoose = require('mongoose');
const entrySchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
title: {type:String},
body: {type:String, required: true},
user: {type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true},
entryImage: {type: String}
}, {collection: 'entries'});
module.exports = mongoose.model('Entry', entrySchema);
Here are my routes for users and entries. You can see how I set up the logic for the associations
USER ROUTES
const express = require('express');
const router = express.Router();
const mongoose = require('mongoose');
const User = require('../models/user');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
router.get('/:userId', (req, res, next) => {
const id = req.params.userId;
User.findById(id)
.select("_id email displayName password entries")
.populate('entry')
.exec()
.then(user => {
res.status(200).json({
id: user._id,
email: user.email,
password: user.password,
entry: user.entry
})
})
.catch(err => {
error: err
})
})
router.post('/signup', (req, res, next) => {
User.find({email: req.body.email})
.exec()
.then(user => {
if(user.length >= 1){
return res.status(422).json({
message: "Username already exists!"
});
} else {
bcrypt.hash(req.body.password, 10, (err, hash) => {
if(err){
return res.status(500).json({
error: err
});
} else {
const user = new User({
_id: new mongoose.Types.ObjectId(),
email: req.body.email,
displayName: req.body.displayName,
password: hash
});
user.save()
.then(data => {
res.status(201).json({
message: "Your user information has been saved in our records",
id: data._id,
email: data.email,
displayName: data.displayName
})
})
.catch(err => {
res.status(500).json({
error: err
})
})
}
})
}
})
.catch(err => {
res.status(500).json({error : err})
})
}); //End of signup post request
EXAMPLE OF AN ENTRY POST REQUEST
EXAMPLE OF A USER POST REQUEST
Please let me know of you have any other questions. Thank you so much, in advance!
The problem is in your schema. You specified explicitly about the _id field.
Your current scheme does not allow mongoose to create this id automatically.
Well, there are two options:
Simplest way. Simply remove _id field from your schema. Mongoose will automatically generate this for you in every create request.
If you want to specify this, pass an option to mongoose so that it can auto-generate this for you
const userSchema = mongoose.Schema({
_id: { type: Schema.ObjectId, auto: true },
})

is there a method to execute a function after a spesific condition in express js

for exemple I have a user model like this
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
username: {
type: String,
required: true,
},
Points: {
type: Number,
default: 0,
},
module.exports = User = mongoose.model("users", UserSchema);
then I want to execute a function automatically when user.points is equal to 10 with express js, is there any solution ?
#Yessine, may you should try something like this. You can add checkForPoints wherever you are updating the Points and proceed with your things,
const { Users } = require('/schema.js');
const checkForPoints = async (username) => {
await Users.findOne({ username }, function (err, data) {
if (err) {
console.log("enter error ------", err)
}
if (data && data.Points === 10) {
// Execute your code
}
});
};
// Users schema(schema.js)
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
mongoose.connect('your db', { useNewUrlParser: true });
const requestSchema = mongoose.Schema({
_id: mongoose.Types.ObjectId,
username: String,
Points: Number
});
module.exports = mongoose.model('users', requestSchema);
Polling is a technique where we check for fresh data over a given interval by periodically making API requests to a server.enables you to periodically check for the newest values of data and do further requests once it enters the desired state.

How can I create a post, i have two mongoose models ref'ing each other

Basically I wanna create a Post that has its author to the users name, the one who created it. I also want the Post to be pushed into the array of posts, which the user model has, that is ref'ing to "Post".
I have been googling and watching youtube videos but still i do not understand how i would go about to do this, also i read about populate, but i wanna create a new post and have the author to be the users name, also i want the post to be pushed into the array of posts that the user has.
How would I go about doing this ?
This is the post create controller
exports.postCreatePost = (req, res, ) => {
const {
title,
description,
context
} = req.body;
const post = new Post({
title,
description,
context,
author:
})
}
This is the model.js
const mongoose = require("mongoose"),
Schema = mongoose.Schema,
bcrypt = require("bcryptjs");
const postSchema = new Schema({
title: String,
description: String,
context: String,
author: {
type: Schema.Types.ObjectId,
ref: "User"
}
});
const userSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true
},
posts: [{
type: Schema.Types.ObjectId,
ref: "Post"
}]
});
userSchema.pre("save", async function save(next) {
const user = this;
if (!user.isModified("password")) return next();
const hashedPassword = await bcrypt.hash(user.password, 10);
user.password = hashedPassword;
next();
});
const Post = mongoose.model("Post", postSchema);
const User = mongoose.model("User", userSchema);
const userId = new mongoose.Types.ObjectId();
Either let client send you username/id and get it from req.body or when you are authenticating user simply pass the reference to the user to the body of request.
For example when client gives you id/username you can do something like this
const post = new Post({
title,
description,
context,
author: req.body.username or req.body.id
})
If you want to push use this
await findOneAndUpdate({_id: req.body.id}, {
"$push":
{
"posts": post._id
}
})

How to Post and Get Data Unique to Each Logged In User with Express and Mongoose?

I am currently working on a small single page app that lets users login with PassportJs and Mongoose.
One of the things I am trying to do is allow users to login and each user has a unique todo/task list which are items associated to that user.
I have been able to do the first part...users can login and express/passport session is accessed using jade #{user.username}, so when logged in the user see "Welcome, [user.username]".
Now I add a form (accessible when user logged in) and the form says undefined. I'm not sure if its my Mongoose schema design or Routes that are causing the problem. Thanks for reading this and here is my code:
Mongoose Schema
mongoose.connect('mongodb://localhost/poplivecore')
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var user = new Schema({
username: String,
password: String,
email: String,
todos: [Todo]
});
var Todo = new Schema({
name: {type: String, default : ''},
user: {type: Schema.ObjectId, ref: 'user'},
createdAt : {type : Date, default : Date.now}
})
var Todo = mongoose.model('Todo', Todo);
var user = mongoose.model('user', user);
Here are my Express routes:
//WORKING....This route is the one that a logged in user sees, form posts with
app.get('/home', ensureAuthenticated ,function(req, res){
res.render('home', { user: req.user});
});
//WORKING...This route allows user to post/submit the login
app.post('/login',
passport.authenticate('local', { failureRedirect: '/login', failureFlash: true }),
function(req, res) {
res.redirect('/home');
});
//WORKING....This route allows user to create a user/account
app.post('/create', function(req, res, next){
var user = new user({
"username": req.body.username,
"password" : req.body.password,
"email" : req.body.email});
user.save(function (err) {
if (!err) {
res.redirect('/home');
}
else {
res.redirect('/');
}
});
});
**//NOT WORKING..Post used in the form inside the logged in Area, that adds a 'todo'**
app.post('/todo', function(req, res){
var todo = new todo(req.body.name);
todo.save(function (err) {
if (!err) {
res.redirect('/home');
}
else {
res.redirect('/fail');
}
});
});
Jade Form, for Adding a todo
enter code here
form(method='post', action='/todo')
//input(type='hidden', value= user._id)#userId
fieldset
label Todo
div.input
input(name='todo.name', type='todo.name', class='xlarge')
div.actions
input(type='submit', value='Save', class='btn primary')
button(type='reset', class='btn') Cancel
I can post on github if you need to see more code...thanks.
Update as per 'numbers1311407' suggesion
*New post route for todo, also changed todo to 'Todo' in both schema and routes*
app.post('/todo', function(req, res){
var todo = new Todo({name : req.body["Todo.name"]});
todo.save(function (err) {
if (!err) {
res.redirect('/home');
}
else {
res.redirect('/fail');
}
});
});
There are at least two problems here that would cause this to not work:
The name of the input passed by your form is todo.name, and you're referencing it as req.body.name in the route.
mongoose models are instantiated with an attributes object, but you're just giving it a string (which, actually, is null currently because of the first issue).
So for your route to work it would look more like this:
app.post("/todo", function (req, res) {
var todo = new Todo({name: req.body["todo.name"]});
todo.user = req.user._id;
// ...
});
If you wanted to pass todo attributes as a parameter object, you'd want to name them with brackets todo[name], rather than dots. This would result in the todo attributes being on object on the req.body, e.g.:
app.post("/todo", function (req, res) {
console.log(req.body.todo); //=> { name: "whatever" }
// ... which means you could do
var todo = new Todo(req.body.todo);
todo.user = req.user._id;
// ...
});
Some other things you might want to change:
As #NilsH points out, you don't want to pass the user id in the form, as that would allow anyone to make a todo for anyone else just by knowing their ID. Rather since you're using passport, make use of the user in the session. You should have access to the user ID through the passport determined user, like req.user._id. I added this to both examples above.
The type of your form input is todo.name. It should be text (that's what the browser is treating it as anyway).
Not necessarily an error, but model names are conventionally capitalized. This also solves an issue your code has above in that you're redefining todo when you say var todo = new todo(...).

Categories