Cannot add property to mongoose document with findOneAndUpdate - node.js

My express app tries to record the login time of the user using Mongoose's findOneAndUpdate.
router.post('/login', passport.authenticate('local', {
failureFlash: true,
failureRedirect: '/'
}), async(req, res, next) => {
// if we're at this point in the code, the user has already logged in successfully.
console.log("successful login")
// save login time to database
const result = await User.findOneAndUpdate({ username: req.body.username }, { loginTime: Date.now() }, { new: true });
console.log(result);
return res.redirect('/battle');
})
The user document does not start out with a login time property. I'm expecting this code to insert that property for me.
The actual result is, the console shows the user document being printed out, but without any added login time property. How can I fix this so a login time property is inserted into the document? Is the only way to do it by defining a login time property in the original mongoose schema? And if so, doesn't that nullify the supposed advantage of NoSQL vs SQL in that it's supposed to allow new unexpected property types into your collections and documents?

I've found the answer for anyone who might come across the same problem. It is not at all possible to add a property to a Mongoose collection if it is not already defined in the Schema. So to fix it I added the property in the Schema.

In fact, you can add a new property that isn't defined in the schema, without modifying the schema. You need to set the flag strict to false to enable this mode. See document here.
The code below demonstrates what I said, feel free to runs it:
const mongoose = require('mongoose');
// connect to database
mongoose.connect('mongodb://localhost/test', { useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false });
// define the schema
const kittySchema = new mongoose.Schema({
name: String
// this flag indicate that the shema we defined is not fixed,
// document in database can have some fields that are not defined in the schema
// which is very likely
}, { strict: false });
// compile schema to model
const Kitten = mongoose.model('Kitten', kittySchema);
test();
async function test() {
// empty the database
await Kitten.deleteMany({});
// test data
const dataObject = { name: "Kitty 1" };
const firstKitty = new Kitten(dataObject);
// save in database
await firstKitty.save();
// find the kitty from database
const firstKittyDocument = await Kitten.findOne({ name: "Kitty 1" });
console.log("Mongoose document", firstKittyDocument);
// modify the kitty, add new property doesn't exist in the schema
const firstKittyDocumentModified = await Kitten.findOneAndUpdate(
{ _id: firstKittyDocument._id },
{ $set: { age: 1 } },
{ new: true }
);
console.log("Mongoose document updated", firstKittyDocumentModified);
// note : when we log the attribute that isn't in the schema, it is undefined :)
console.log("Age ", firstKittyDocumentModified.age); // undefined
console.log("Name", firstKittyDocumentModified.name); // defined
// for that, use .toObject() method to convert Mongoose document to javascript object
const firstKittyPOJO = firstKittyDocumentModified.toObject();
console.log("Age ", firstKittyPOJO.age); // defined
console.log("Name", firstKittyPOJO.name); // defined
}
The output:
Mongoose document { _id: 60d1fd0ac3b22b4e3c69d4f2, name: 'Kitty 1', __v: 0 }
Mongoose document updated { _id: 60d1fd0ac3b22b4e3c69d4f2, name: 'Kitty 1', __v: 0, age: 1 }
Age undefined
Name Kitty 1
Age 1
Name Kitty 1

Related

How does mongoose know what collection I am accessing?

I'm having a trouble grasping a concept in Mongoose.
I'm using MongoDB atlas, got a cluster , a database and 2 collections.
users, characters.
Through a guide I've learned that a good way to write your stuff is to have a model (I use the naming schema) as a file, importing it into your Database module/class
and using it there to perform a query...
const mongoose = require("mongoose");
const process = require("./config.env");
db = () => {
return mongoose
.connect(process.env.URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: true,
})
.then((response) => {
console.log(`Connected to Databse : ${response.connection.host}`);
})
.catch((err) => {
console.log("DB_ERROR:", err);
process.exit(1);
});
};
module.exports = db;
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
});
const User = mongoose.model("User", UserSchema);
module.exports = User;
const User = require("../schemas/User");
const db = require("../config/db");
class Database {
constructor(db, collection) {
this.db = db;
this.collection = collection;
this.User = User;
}
connect() {
return db();
}
}
module.exports = Database;
one file to handle the db connection..another file as the User schema and a third file to handle every function i might use globally...
One thing I cannot wrap my mind around is
how is the findOne() function able to locate the collection I am using without me telling it what collection i want it to search in?
is it somehow translating the
const User = mongoose.model("User", UserSchema);
line and searching for "users" as well? I just can't understand the magic behind this...
what if I want to search specifically in the characters collection...?
Mongoose uses the model name, as passed when it was created: mongoose.model("User", UserSchema), converted to lower case and with an 's' appended.
For the model User it uses the collection users by default. You can change this by explicitly specifying the collection name in the schema.

Problem with a Cast using Router.put with mongoDB in MERN app

I want to attach the router.put which will update the Boolean(isOn) in toggle button but firstly I wanted to try how it works and now I am facing the problem.
const express = require("express");
const router = express.Router();
const Buttons = require('../../models/Buttons');
// GET buttons
// This request works perfect
router.get('/', (req,res) => {
Buttons.find()
.sort({name: 1})
.then(buttons => res.json(buttons))
});
// PUT buttons
// This one doesnt work at all
router.put('/:name', function(req,res,next) {
Buttons.findByIdAndUpdate({name: req.params.name},
req.body).then(function(){
Buttons.findOne({name: req.params.name}).then(function(buttons){
res.send(buttons);
});
});
});
module.exports = router;
Model of buttons has only name: String, required: true and isOn: Boolean, required: true and data in db looks like that:
Can you tell me what did I do wrong here?
Code of Buttons modal :
const mongoose = require('mongoose');
const Schema = mongoose.Schema
const buttonSchema = new Schema ({
name: {
type: String,
required: true
},
isOn: {
type: Boolean,
required: true
}
});
module.exports = Buttons = mongoose.model("buttons", buttonSchema);
You ca only use findByIdAndUpdate when you want to update the document by matching the _id of the document
If you want to match the document by any other property (such as name in your case), you can use findOneAndUpdate
Write your query like this
router.put('/:name', function(req,res,next) {
Buttons.findOneAndUpdate({name: req.params.name},
req.body).then(function(){
Buttons.findOne({name: req.params.name}).then(function(buttons){
res.send(buttons);
});
});
});
Hope this helps
Please add your id as well which you have to update in your database
Model.findByIdAndUpdate(id, updateObj, {new: true}, function(err, model) {...
This error occur because findByIdAndUpdate need id of an Object which we want to update so it shows ObjectId error. so pass your id from front end and use it in your back-end to update particulate data.
step 1 : you can create new endpoint for update-name
router.put('/update-name', function(req,res,next) {
//here you can access req.body data comes from front-end
// id = req.body.id and name = req.body.name then use it in your
Buttons.findByIdAndUpdate(id, { name : name }, {new: true}, function(err, model) {...
}
step 2 : try this endpoint /update-name and pass your data in Body from postman

Mongodb schema emptied after seconds

When i am making a request to save a new object to my mongodb, it gets saved, and after seconds everything in that schema disappears.
In the screenshot below you can see this happening, where with the first command i check that the schema is empty, then i make a request to save a new object which is done successfully, and after a few seconds you can see that the object has disappeared.
The express endpoint looks like so:
router.post('/bookdate',passport.authenticate('jwt', {session:false}), (req, res) => {
const userId = req.user._id
const appartmentNumber = req.user.apartmentNumber;
const requestedDate = req.body.requestedDate;
const bookingZone = req.body.bookingZone;
const newBooking = new Booking({
'apartmentNumber': appartmentNumber,
'dateOfBooking': requestedDate,
'bookingZone': bookingZone
});
if (req.user.hasTimeBooked) {
res.json({booked: false, msg: 'There is already a booking for this user.'})
} else {
if (typeof newBooking.requestedDate !== undefined && typeof newBooking.bookingZone !== undefined) {
Booking.addBooking(newBooking, (err, result)=>{
if(err){
res.json({booked: false, msg: err})
} else {
res.json({booked: true, msg: result})
}
})
} else {
res.json({booked: false, msg: 'Undefined parameters Date or Zone'})
}
}
});
and the mongoose schema looks like so
const mongoose = require('mongoose');
const config = require('../config/database');
const BookingSchema = mongoose.Schema({
apartmentNumber:{
type: Number,
unique: true
},
dateOfBooking:{
type: Date
},
bookingZone:{
type: String
}
});
const Booking = module.exports = mongoose.model('bookings',BookingSchema, 'bookings');
module.exports.addBooking = function(bookingObj, cb){
var newBooking = new Booking(bookingObj);
newBooking.save(cb);
}
There are no errors appearing in console, and i am not quite sure where to start looking.
Thanks in advance!
EDIT
The result from db.bookings.getIndices() is shown in the screenshot here
From the getIndices output I could see you've created an TTL index on dateOfBooking so it gets deleted after 60 seconds in the backend
From the mongo docs TTL index
TTL indexes are special single-field indexes that MongoDB can use to
automatically remove documents from a collection after a certain
amount of time or at a specific clock time

Need a better way to fill values from req.body into a model

I am creating a webapp using the following stack:
Node
Express
MongoDB
Mongoose
I have structured the app into a MVC structure. In the app I need to get create (post) and update (put) data values which I get from res.body and copy them to Mongoose Model. For example I am doing the following:
Mongoose Model:
let mongoose = require('mongoose');
let customerPaymentType = mongoose.Schema({
type: { type: String, required: true, unique: true}
},
{
timestamps: true
}
);
module.exports = mongoose.model('CustomerPaymentType', customerPaymentType);
Controller (Only a part):
let mongoose = require('mongoose');
let CustomerPaymentType = mongoose.model('CustomerPaymentType');
class CustomerPaymentTypeController {
constructor(){}
create(req, res){
let customerPaymentType = new CustomerPaymentType();
this._setCustomerPaymentType(req.body, customerPaymentType);
customerPaymentType.save(error=>{
if (error) res.send(error);
res.json({
message: 'Customer payment type successfully created',
customerPaymentType:{_id: customerPaymentType._id}
});
});
}
//private methods
_setCustomerPaymentType(rawCustomerPaymentType, customerPaymentType){
if (typeof rawCustomerPaymentType.type !== 'undefined') customerPaymentType.type = rawCustomerPaymentType.type.trim();
}
}
module.exports = CustomerPaymentTypeController;
In this model there is only one field, thus populating the model with the data from the req.body in the controller file is easy. But I have other models with more than 30 fields, and it is taking long time to populate them. Is there any easier way to deal with repopulating a model, similar to one present in Ruby on Rails?

Mongoose - inserting subdocuments

I have a user model, and a log model. The log model is a subdocument of user model. So in my user model I have:
var mongoose = require('mongoose');
var Log = require('../models/log');
var UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true
},
logsHeld: [
Log
]
});
Then in my 'Log' model I have:
var mongoose = require('mongoose');
var logSchema = new mongoose.Schema({
logComment: {
type: String,
},
});
module.exports = mongoose.model('Log', logSchema);
So upon creation of a 'user', the 'logsHeld' always begins empty. I want to know how to add subdocuments to this user model.
I've tried doing this POST method:
router.post('/createNewLog', function(req, res) {
var user = new User ({
logssHeld: [{
logComment: req.body.logComment
}]
});
user.save(function(err) {
if(err) {
req.flash('error', 'Log was not added due to error');
return res.redirect('/home');
} else {
req.flash('success', 'Log was successfully added!');
return res.redirect('/home');
}
});
});
But this doesn't work. It also includes a 'new User' line, which I don't think I need given this would be for an existing user.
You need to use the logSchema instead of the Log model as your subdocument schema in User model. You can access the schema as follows:
var mongoose = require('mongoose');
/* access the Log schema via its Model.schema property */
var LogSchema = require('../models/log').schema; // <-- access the schema with this
var UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true
},
logsHeld: [LogSchema]
});
Picking up from your comments in another answer where you are facing another issue
WriteError({"code":11000,"index":0,"errmsg":"E11000 duplicate key
error index: testDB.users.$email_1 dup key:
you are getting this because there's already a document in your users collection that has most probably a null value on the email field. Even though your schema does not explicitly specify an email field, you may have an existing old and unused unique index on users.email.
You can confirm this with
testDB.users.getIndexes()
If that is the case and manually remove the unwanted index with
testDB.users.dropIndex(<index_name_as_specified_above>)
and carry on with the POST to see if that has rectified the error, I bet my $0.02 that there is an old unused unique index in your users collection which is the main issue.
Try using logSchema which references only the subdocument schema, Log refers to the entire contents of ../models/log
var UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true
},
logsHeld: [
logSchema
]
});
Documentation: http://mongoosejs.com/docs/subdocs.html
Try push to insert item in array in mongoose
var user = new User;
user.logssHeld.push({
logComment: req.body.logComment
});
user.save(function(err, doc) {
//DO whatever you want
});
see the docs here

Resources