MongoDB - Mongoose - TypeError: save is not a function

MongoDB - Mongoose - TypeError: save is not a function



I am attempting to perform an update to a MongoDB document (using mongoose) by first using .findById to get the document, then updating the fields in that document with new values. I am still a bit new to this so I used a tutorial to figure out how to get it working, then I have been updating my code for my needs. Here is the tutorial: MEAN App Tutorial with Angular 4. The original code had a schema defined, but my requirement is for a generic MongoDB interface that will simply take whatever payload is sent to it and send it along to MongoDB. The original tutorial had something like this:


exports.updateTodo = async function(todo)
var id = todo.id

try
//Find the old Todo Object by the Id

var oldTodo = await ToDo.findById(id);
catch(e)
throw Error("Error occured while Finding the Todo")


// If no old Todo Object exists return false
if(!oldTodo)
return false;


console.log(oldTodo)

//Edit the Todo Object
oldTodo.title = todo.title
oldTodo.description = todo.description
oldTodo.status = todo.status


console.log(oldTodo)

try
var savedTodo = await oldTodo.save()
return savedTodo;
catch(e)
throw Error("And Error occured while updating the Todo");




However, since I don't want a schema and want to allow anything through, I don't want to assign static values to specific field names like, title, description, status, etc. So, I came up with this:


exports.updateData = async function(update)
var id = update.id

// Check the existence of the query parameters, If they don't exist then assign a default value
var dbName = update.dbName ? update.dbName : 'test'
var collection = update.collection ? update.collection : 'testing';

const Test = mongoose.model(dbName, TestSchema, collection);

try
//Find the existing Test object by the Id
var existingData = await Test.findById(id);
catch(e)
throw Error("Error occurred while finding the Test document - " + e)


// If no existing Test object exists return false
if(!existingData)
return false;


console.log("Existing document is " + existingData)

//Edit the Test object
existingData = JSON.parse(JSON.stringify(update))

//This was another way to overwrite existing field values, but
//performs a "shallow copy" so it's not desireable
//existingData = Object.assign(, existingData, update)

//existingData.title = update.title
//existingData.description = update.description
//existingData.status = update.status

console.log("New data is " + existingData)

try
var savedOutput = await existingData.save()
return savedOutput;
catch(e)
throw Error("An error occurred while updating the Test document - " + e);




My original problem with this was that I had a lot of issues getting the new values to overwrite the old ones. Now that that's been solved, I am getting the error of "TypeError: existingData.save is not a function". I am thinking the data type changed or something, and now it is not being accepted. When I uncomment the static values that were in the old tutorial code, it works. This is further supported by my console logging before and after I join the objects, because the first one prints the actual data and the second one prints [object Object]. However, I can't seem to figure out what it's expecting. Any help would be greatly appreciated.



EDIT: I figured it out. Apparently Mongoose has its own data type of "Model" which gets changed if you do anything crazy to the underlying data by using things like JSON.stringify. I used Object.prototype.constructor to figure out the actual object type like so:


console.log("THIS IS BEFORE: " + existingData.constructor);
existingData = JSON.parse(JSON.stringify(update));
console.log("THIS IS AFTER: " + existingData.constructor);



And I got this:


THIS IS BEFORE: function model(doc, fields, skipId)
model.hooks.execPreSync('createModel', doc);
if (!(this instanceof model))
return new model(doc, fields, skipId);

Model.call(this, doc, fields, skipId);

THIS IS AFTER: function Object() [native code]



Which showed me what was actually going on. I added this to fix it:


existingData = new Test(JSON.parse(JSON.stringify(update)));



On a related note, I should probably just use the native MongoDB driver at this point, but it's working, so I'll just put it on my to do list for now.






Just a little side note - if you are using node 8 or above try to use 'let' and 'const' instead of 'var' - see: medium.com/javascript-scene/…

– Matthew P
Sep 16 '18 at 21:39






Can you include what the console.logs output?

– Matthew P
Sep 16 '18 at 21:58






This was really frustrating, but I figured it out. So apparently the mongoose has a data type of [object Model] and when you use JSON.stringify, it converts it to [object Object], which makes mongoose angry. I just had to re-cast the data to a mongoose model and that fixed it. Although now that has me thinking I should just throw my hands up and use a native MongoDB driver instead of mongoose. I keep meaning to convert the 'vars' in my "borrowed" code to 'let' or 'const', but I keep forgetting to do it. I'll have to read up on which is preferable and when to use them. Thanks for the help!

– NellaGnute
Sep 16 '18 at 22:01






I would suggest using the MonogoDB driver instead of Mongoose, it's faster, supported by MonogoDB directly and in my opinion is no harder to use - I'll provide an example tomorrow. Also, is there any reason for you to find the document before you update it? i.e. you already have the document id so you could just do one call to update the document - at the moment you do two which seems a waste of resources. When you are updating the document are you intending to replace the whole document or just updating the fields that have changed?

– Matthew P
Sep 16 '18 at 22:03






Great - I was thinking along those lines

– Matthew P
Sep 16 '18 at 22:04




1 Answer
1



You've now found a solution but I would suggest using the MongoDB driver which would make your code look something along the lines of this and would make the origional issue disappear:


// MongoDB Settings
const MongoClient = require(`mongodb`).MongoClient;
const mongodb_uri = `mongodb+srv://$REPLACE_mongodb_username:$REPLACE_mongodb_password@url-here.gcp.mongodb.net/test`;
const db_name = `test`;
let db; // allows us to reuse the database connection once it is opened

// Open MongoDB Connection
const open_database_connection = async () =>
try
client = await MongoClient.connect(mongodb_uri);
catch (err) throw new Error(err);
db = client.db(db_name);
;


exports.updateData = async update =>

// open database connection if it isn't already open
try
if (!db) await open_database_connection();
catch (err) throw new Error(err);


// update document
let savedOutput;
try
savedOutput = await db.collection(`testing`).updateOne( // .save() is being depreciated
// filter
_id: update.id // the '_id' might need to be 'id' depending on how you have set your collection up, usually it is '_id'
,
$set: // I've assumed that you are overwriting the fields you are updating hence the '$set' operator
update // update here - this is assuming that the update object only contains fields that should be updated


// If you want to add a new document if the id isn't found add the below line
// , upsert: true

);
catch (err) throw new Error(`An error occurred while updating the Test document - $err`);


if (savedOutput.matchedCount !== 1) return false; // if you add in ' upsert: true ' above, then remove this line as it will create a new document

return savedOutput;



The collection testing would need to be created before this code but this is only a one-time thing and is very easy - if you are using MongoDB Atlas then you can use MongoDB Compass / go in your online admin to create the collection without a single line of code...


testing



As far as I can see you should need to duplicate the update object. The above reduces the database calls from 2 to one and allows you to reuse the database connection, potentially anywhere else in the application which would help to speed things up. Also don't store your MongoDB credentials directly in the code.


update






This appears to work on its own, which is awesome. I'm curious as to how this would be broken up into a service (I'm using Angular). Currently the tutorial code I used for the Mongoose way of doing it has the connection happening in app.js and all of the REST calls are pointed to a controller in the controller.js file. This file has basic REST calls which then point to code in a service.js file that has the actual logic that it uses to do queries, updates, etc. This is probably a basic Angular concept that I'm not understanding, but I figured you would probably have some insight on it.

– NellaGnute
Sep 19 '18 at 12:51






I'm afraid I'm not an Angular user so can 't help there. It is usually best to reuse connections rather than open a new connection for every call hence the way done in this example as this is usually a fairly resource heavy action.

– Matthew P
Oct 1 '18 at 14:08




Thanks for contributing an answer to Stack Overflow!



But avoid



To learn more, see our tips on writing great answers.



Required, but never shown



Required, but never shown




By clicking "Post Your Answer", you agree to our terms of service, privacy policy and cookie policy

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)