I'm running some project on MEAN.js and I've got a following problem. I want to make some user's profile calculation and the save it to database. But there's a problem with method in users model:
UserSchema.pre('save', function(next) {
if (this.password && this.password.length > 6) {
this.salt = new Buffer(crypto.randomBytes(16).toString('base64'), 'base64');
this.password = this.hashPassword(this.password);
}
next();
});
If I will send a password with my changes, it will change credentials, so user is unable to login next time. I want to delete password from user object before save, but I'm not able to do it (let's look at the comments in my code below):
exports.signin = function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err || !user) {
res.status(400).send(info);
} else {
/* Some calculations and user's object changes */
req.login(user, function(err) {
if(err) {
res.status(400).send(err);
} else {
console.log(delete user.password); // returns true
console.log(user.password); // still returns password :(
//user.save();
//res.json(user);
}
});
}
})(req, res, next);
};
What's wrong? Why the delete method returns true, but nothing happens? Thanks for your help :)
Just do:
user.password = undefined;
instead of:
delete user.password;
and the password property will not appear at the output.
there are certain rules for delete operator in javascript
if the property is an own non-configurable property in "strict mode" than it will return false.
for example
x = 42; // creates the property x on the global object
var y = 43; // creates the property y on the global object, and marks it as non-configurable
// x is a property of the global object and can be deleted
delete x; // returns true
// y is not configurable, so it cannot be deleted
delete y; // returns false
If the object inherits a property from a prototype, and doesn't have the property itself, the property can't be deleted by referencing the object. You can, however, delete it directly on the prototype.
for example
function Foo(){}
Foo.prototype.bar = 42;
var foo = new Foo();
// returns true, but with no effect,
// since bar is an inherited property
delete foo.bar;
// logs 42, property still inherited
console.log(foo.bar);
so, please cross check these point and for more information your can read this Link
Had a similar problem. This worked for me:
// create a new copy
let newUser= ({...user}._doc);
// delete the copy and use newUser that thereafter.
delete newUser.password;
Working with MONGOOSE?
If you're facing this issue when working with Mongoose (Mongo DB's upper layer) then you can use lean property on find method
Examples
Without lean (The keys won't be deleted)
const users = await User.find({ role: 'user' }) // no lean method
users.forEach((user) => {
delete user.password // doesn't delete the password
})
console.log(users)
/* [
{name:'John', password:'123'},
{name:'Susan', password:'456'}
]
*/
With lean (The keys get deleted)
const users = await User.find({ role: 'user' }).lean()
users.forEach((user) => {
delete user.password // deletes the password
})
console.log(users)
/* [
{name:'John'},
{name:'Susan'}
]
*/
Reason why lean works
Documents returned from queries with the lean option enabled are plain javascript objects, not Mongoose Documents. They have no save method, getters/setters, virtuals, or other Mongoose features.
Documents are kind of read-only, so delete doesn't work on them
Reference - https://stackoverflow.com/a/48137096/10824697
https://mongoosejs.com/docs/api.html#query_Query-lean
Method 2 without lean
If you want to use the mongoose provided method to remove some property while you are querying, you can remove with select method,
const users = await User.find({ role: 'user' }).select('-password')
console.log(users)
/* [
{name:'John'},
{name:'Susan'}
]
*/
The answer above from Majed A is the simplest solution that works for single objects properties, we can even make it for more easier by removing the ...user spreader. just delete the property from your object._doc sub-object. in your example it would have been:
user.save()
delete user._doc.password
res.status(201).json(user) // The password will not be shown in JSON but it has been saved.
Had a similar issue. The delete operator "was not working" when trying to delete a property from an object in a specific case. Fixed it using Lodash unset:
_.unset(user, "password");
https://lodash.com/docs/4.17.11#unset
Otherwise the delete operator does work. Just in case, delete operator docs here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete
If password was defined with defineProperty, then configurable defaults to false if not set. In that case, then the property can't be deleted.
For me, node js still tells me the property was deleted (console.log(delete obj.prop)), but it wasn't deleting.
function test(settings) {
settings = {...{c: false, w:false}, ...settings}
let obj = {}
Object.defineProperty(obj, "prop", {
configurable: settings.c,
enumerable: settings.e ?? true,
writable: settings.w,
value: "foo"
});
console.log(
JSON.stringify(settings),
'\nset value to 1', (function() {obj.prop = 1})() || "",
'\nappended bar:', (function() {obj.prop += "bar"})() || "",
'\nobj:', JSON.stringify(obj),
'\ndelete:', delete obj['prop'],
'\nobj:', JSON.stringify(obj))
}
console.log('baseline: unchangeable, undeletable');
test()
console.log('unchangeable, deletable');
test({c: true})
console.log('changeable, undeletable');
test({w: true})
console.log('changeable, deletable');
test({c: true, w: true})
You may use this. It skips the unwanted key instead of deleting, it then returns an object.
let x = {1:'1', 2:2}
console.log('in', x)
function remove(Object, key){
let outputObject = {}
for (let inKey in Object){
if(key == inKey){
console.log(key , 'was deleted')
}else{
outputObject[inKey] = Object[inKey]
}
}
return outputObject
}
let out = remove(x, 1)
console.log('out', out)
The most likely, property which you want to delete has not owned the property for this object. In this case, the result of the operation will show true but nothing will be deleted.
Related
My Mongoose schema uses a custom _id value and the code I inherited does something like this
const sampleSchema = new mongoose.Schema({
_id: String,
key: String,
});
sampleSchema.statics.generateId = async function() {
let id;
do {
id = randomStringGenerator.generate({length: 8, charset: 'hex', capitalization: 'uppercase'});
} while (await this.exists({_id: id}));
return id;
};
let SampleModel = mongoose.model('Sample', sampleSchema);
A simple usage looks like this:
let mySample = new SampleModel({_id: await SampleModel.generateId(), key: 'a' });
await mySample.save();
There are at least three problems with this:
Every save will require at least two trips to the database, one to test for a unique id and one to save the document.
For this to work, it is necessary to manually call generateId() before each save. An ideal solution would handle that for me, like Mongoose does with ids of type ObjectId.
Most significantly, there is a potential race condition that will result in duplicate key error. Consider two clients running this code. Both coincidentally generate the same id at the same time, both look in the database and find the id absent, both try to write the record to the database. The second will fail.
An ideal solution would, on save, generate an id, save it to the database and on duplicate key error, generate a new id and retry. Do this in a loop until the document is stored successfully.
The trouble is, I don't know how to get Mongoose to let me do this.
Here's what I tried: Based on this SO Question, I found a rather old sample (using a very old mongoose version) of overriding the save function to accomplish something similar and based this attempt off it.
// First, change generateId() to force a collision
let ids = ['a', 'a', 'a', 'b'];
let index = 0;
let generateId = function() {
return ids[index++];
};
// Configure middleware to generate the id before a save
sampleSchema.pre('validate', function(next) {
if (this.isNew)
this._id = generateId();
next();
});
// Now override the save function
SampleModel.prototype.save_original = SampleModel.prototype.save;
SampleModel.prototype.save = function(options, callback) {
let self = this;
let retryOnDuplicate = function(err, savedDoc) {
if (err) {
if (err.code === 11000 && err.name === 'MongoError') {
self.save(options, retryOnDuplicate);
return;
}
}
if (callback) {
callback(err, savedDoc);
}
};
return self.save_original(options, retryOnDuplicate);
}
This gets me close but I'm leaking a promise and I'm not sure where.
let sampleA = new SampleModel({key: 'a'});
let sampleADoc = await sampleA.save();
console.log('sampleADoc', sampleADoc); // prints undefined, but should print the document
let sampleB = new SampleModel({key: 'b'});
let sampleBDoc = await sampleB.save();
console.log('sampleBDoc', sampleBDoc); // prints undefined, but should print the document
let all = await SampleModel.find();
console.log('all', all); // prints `[]`, but should be an array of two documents
Output
sampleADoc undefined
sampleBDoc undefined
all []
The documents eventually get written to the database, but not before the console.log calls are made.
Where am I leaking a promise? Is there an easier way to do this that addresses the three problems I outlined?
Edit 1:
Mongoose version: 5.11.15
I fixed the problem by changing the save override. The full solution looks like this:
const sampleSchema = new mongoose.Schema({
_id: String,
color: String,
});
let generateId = function() {
return randomStringGenerator.generate({length: 8, charset: 'hex', capitalization: 'uppercase'});
};
sampleSchema.pre('validate', function() {
if (this.isNew)
this._id = generateId();
});
let SampleModel = mongoose.model('Sample', sampleSchema);
SampleModel.prototype.save_original = SampleModel.prototype.save;
SampleModel.prototype.save = function(options, callback) {
let self = this;
let isDupKeyError = (error, field) => {
// Determine whether the error is a duplicate key error on the given field
return error?.code === 11000 && error?.name === 'MongoError' && error?.keyValue[field];
}
let saveWithRetries = (options, callback) => {
// save() returns undefined if used with callback or a Promise otherwise.
// https://mongoosejs.com/docs/api/document.html#document_Document-save
let promise = self.save_original(options, callback);
if (promise) {
return promise.catch((error) => {
if (isDupKeyError(error, '_id')) {
return saveWithRetries(options, callback);
}
throw error;
});
}
};
let retryCallback;
if (callback) {
retryCallback = (error, saved, rows) => {
if (isDupKeyError(error, '_id')) {
saveWithRetries(options, retryCallback);
} else {
callback(error, saved, rows);
}
}
}
return saveWithRetries(options, retryCallback);
}
This will generate an _id repeatedly until a successful save is called and addresses the three problems outlined in the original question:
The minimum trips to the database has been reduced from two to one. Of course, if there are collisions, more trips will occur but that's the exceptional case.
This implementation takes care of generating the id itself with no manual step to take before saving. This reduces complexity and removes the required knowledge of prerequisites for saving that are present in the original method.
The race condition has been addressed. It won't matter if two clients attempt to use the same key. One will succeed and the other will generate a new key and save again.
To improve this:
There ought to be a maximum number of save attempts for a single document followed by failure. In this case, you've perhaps used up all the available keys in whatever domain you're using.
The unique field may not be named _id or you might have multiple fields that require a unique generated value. The embedded helper function isDupKeyError() could be updated to look for multiple keys. Then on error you could add logic to regenerate just the failed key.
I'm hashing passwords on a pre hook with mongoose schemas, however, the check I'm doing for isModified to know whether or not I should hash/rehash the password with isModified is always resulting in false.
await mongoose.connect(this.connUri, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true
});
const oldUser = await UserModel.findOne({ name: user.name });
oldUser.name = user.name || oldUser.name;
oldUser.password = user.password || oldUser.password;
oldUser.firstName = user.firstName || oldUser.firstName;
oldUser.lastName = user.lastName || oldUser.lastName;
oldUser.email = user.email || oldUser.email;
oldUser.status = user.status || oldUser.status;
let modified = oldUser.isModified(); // test for seeing if modified or not. Always false
await oldUser.save();
result.status = status;
result.result = oldUser;
await mongoose.disconnect();
An fyi user is passed into the method housing this. I would've thought that by changing the properties it would be marked as isModified === true so how does one get the isModified set to true/false or what actually sets it? Any tips, suggestions, or advice appreciated. Open to a different way of doing this too, thanks!
Edit - based on comments and suggested similar answer
Even with the specified object parameter my code is still only presenting as false instead of true in the isModified() method. So the suggested questions isn't helpful because it doesn't answer how the modified property gets set in the first place.
If one uses oldUser.set('password', user.password || oldUser.password); for each of the properties then the object gets isModifed() === true however, when you set it directly with document.property = 'some value' then the object doesn't get isModified() === true but insetad is false, even though it did change the property, and will save it in the DB with .save(). So why is that, and what's the better way to update a document with mongoose?
I've made a full working example from scratch to test this, you can find the repo here:
https://github.com/ZeldOcarina/mongoose-edit-example
The main takeaway is this handler:
app.patch('/user', async (req, res) => {
const { id, username, password } = req.body;
const user = await User.findById(id);
user.username = username ? username : user.username;
user.password = password ? password : user.password;
await user.save();
res.status(200).json(user);
});
If a value exists it means something came from the form thus it's changed, let me know if this works for you or I dig further into this.
The Solution is why not using conditional about changing property. Example if u want to check password isModified like this :
oldUser.pre('save', function(next) {
if(oldUser.password && oldUser.isModified('password')){
this.password = bcrypt.hashSync(oldUser.password, bcrypt.genSaltSync(8),null);
}
next() });
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.
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...
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.
can't find whether this has been asked before or not, so bear with me.
I'm just starting to use Neo4j with a nodejs backend and the neo4j-driver driver. I wonder if it's possible to create a node with several properties without enumerating each one in the second argument to the session.run method.
app.post("/signup", function(req, res) {
var user = req.body; //{userId: "johnsmith", email: "john#smith.com", ...}
session.run("CREATE (u:User {u}) RETURN u", user).then(function(response) {
/*do stuff with newly created user*/
}, function() {
//handle error
});
});
Currently, this yields the following error: {code: 'Neo.ClientError.Statement.ParameterMissing', message: 'Expected a parameter named u' }, and if I change the above to:
app.post("/signup", function(req, res) {
var user = req.body; //{userId: "johnsmith", email: "john#smith.com", ...}
session.run("CREATE (u:User {u}) RETURN u", {u: user}).then(function(response) {
/*do stuff with newly created user*/
}, function() {
//handle error
});
});
then the error reads: { code: 'Neo.ClientError.Statement.TypeError', message: 'Property values can only be of primitive types or arrays thereof' }.
This doesn't make much sense to me, given that the refcard clearly states you can create a node using a map, like so: CREATE (n {map}); so I must obviously be getting something wrong. I hope I don't have to enumerate all a user's properties like so:
session.run("CREATE (u:User {userId: {u.userId}, email: {u.email}, ...}) RETURN u", {u: user}).then(/*...*/)
Thanks in advance
Map could not be the value of the properties
You can set properties using a parameter - http://neo4j.com/docs/developer-manual/current/cypher/clauses/set/#set-set-all-properties-using-a-parameter
So you need to check the input parameter and transform its properties if necessary.
For example:
app.post("/signup", function(req, res) {
var params = {};
//{userId: "johnsmith", email: "john#smith.com", ...}
Object.keys(req.body).forEach( function(k) {
var value = req.body[k];
if (!isPrimitive(val)) value = JSON.stringify(value);
params[k] = value;
});
session.run("CREATE (u:User) SET u = {user} RETURN u", {user: params})
.then(function(response) {
// do stuff with newly created user
}, function() {
// handle error
});
});
Where isPrimitive an abstract function that checks whether a variable is a primitive.
Neo4j only supports storing specific kinds of data structures to a property. To quote from the Cypher Refcard:
Neo4j properties can be strings, numbers, booleans or arrays thereof.
And, to be more exact, in order for an array (or "collection") to be stored as a property value, all its elements must be of the same primitive type.
The answer from #stdob-- provides one possible simple workaround to this (but it stringifies all arrays, even ones that can be stored without conversion).
NOTE: The refacrd needs to be a bit more clear. Nested maps are supported, in general. For instance, you can freely pass in JSON data as Cypher query parameters. However, maps containing nested maps are NOT supported for storing as property values.
Scroll down to the bottom of this post to see a work around / possible solution.
This is probably easier just to explain in the source code with comments. The issue at hand is I cannot figure out how pseudo classes work together to perform the task I'm trying to do (explained in the code below).
The code is broken down into 3 files: lead.js, router.js, and db.js.
There are a decent amount of lines of code but most of it is comments.
[lead.js]
var bcrypt = require('bcrypt'),
validators = require('../lib/validators'),
utility = require('../lib/utility'),
document = {};
var Lead = module.exports = function (db) {
// Save a reference to the database.
this.db = db;
// Reference initial document.
// This is totally wrong, not sure how to 'send' a variable to the constructor of a class
// when I cannot add another param. Due to how I'm importing the db model, I won't know what
// the document is until I fill out the form. I've also tried 'document' instead of 'Lead.document'.
this.document = Lead.document;
// Setup the document if it exists.
// This also doesn't work.
// Basically I want to be able to set up a document variable outside of this module (line #100),
// Then pass it to this module after filling it up with values from a form.
// Then based on what's been filled in, it would fix up (trim, convert to lower case)
// some of the values automatically and default a few values that I'm not always going to pass.
if (!document) {
var salt = bcrypt.genSaltSync(10),
hash = bcrypt.hashSync(utility.generatePassword(), salt);
// Default values.
if (!document.meta.createdAt) { this.document.meta.createdAt = Date.now(); }
if (!document.login.password) { this.document.login.password = hash; }
if (!document.login.role) { this.document.login.role = 'User'; }
// Normalize a few values.
this.document.login.email = document.login.email.toLowerCase().trim();
this.document.contact.name.first = document.contact.name.first.trim();
this.document.contact.name.last = document.contact.name.last.trim();
this.document.contact.address.street = document.contact.address.street.trim();
this.document.contact.address.city = document.contact.address.city.trim();
this.document.contact.address.state = document.contact.address.state.trim();
this.document.contact.address.zip = document.contact.address.zip.trim();
this.document.contact.phone.home = document.contact.phone.home.trim();
}
// So in regards to the above code, the end result I'm looking for is...
// I want to append some properties to the this.document reference when the document is empty (when I'm updating it, I won't set the document),
// and on new documents it will append a few default values/normalize all the fields.
};
Lead.prototype.validate = function(fn) {
var errors = [];
// Some validation rules I cut out to make this shorter.
if (errors.length) return fn(errors);
fn();
};
Lead.prototype.save = function(fn) {
this.db.collection('leads', function(err, collection) {
if (err) { fn(new Error({message: err})); }
collection.insert(this.document, function(err, result) {
return fn(err, result);
});
});
};
---
[route.js file]
var db = require('../models/db');
app.post('/register', function(req, res) {
var data = req.body.lead || {};
// Fill the document.
var document = {
meta: {
host: req.headers.host,
referer: req.headers.referer,
createdIPAddress: req.connection.remoteAddress
},
login: {
email: data.email
},
contact: {
name: {
first: data.first,
last: data.last
},
address: {
street: data.street,
city: data.city,
state: data.state,
zip: data.zip
},
phone: {
home: data.phone
}
}
};
// Write the document.
db.lead.document = document;
db.lead.validate(function(err) {
if (err) {
req.session.error = err;
return res.redirect('back');
}
db.lead.save(function(err) {
res.redirect('/register/success');
});
});
});
---
[db.js]
var mongodb = require('mongodb'),
server = new mongodb.Server('localhost', 27017),
connection = new mongodb.Db('test', server);
connection.open(function(err, db) {});
module.exports = {
lead: new (require('./lead'))(connection)
};
When I run this, my validator always reports that the password is empty which makes sense. I'm sending the document initially to the class with an empty password (the password is randomly generated, not a form field) -- the problem is I have no idea what to do with the if (!document) ... code block to actually set the this.document properly.
I hope between the comments and code you can get an idea of what I'm trying to do. I've been stuck on this for a while.
EDIT
I changed the flow of it a bit to get a solution.
In the db.js, I exported the connection rather than instantiating the lead (and future models) directly.
In the router.js file, I require the db and lead file, then pass both the db connection and the document in the constructor of the Lead. Ex.
var lead = new Lead(db, document);
In the lead.js file, it becomes as simple as doing this.document = document (same as the db). When I submit a new lead, the values I don't send over from router.js get appended to the document (the created date, a random password, etc.) and everything is good.
Is this a decent way of handling this, or is there a better way to refactor this?
This is completely wrong way even if make this code work as you want. In this example you have singleton lead. By requesting /register url you want to set 'document' field to this singleton . (IMPORTANT) But requests work asynchronously. Absolutely no guarantee that you save the document, which has just validate. Because new request may overwrite it in lead object. You need to do this logic in request scope. One scope for one request. Not one for all.
You need to read up on object-oriented programming in Javascript.
The anonymous function you're defining near the top of your code is the constructor function, so with respect to the document property you want that is currently uninitialized, just type something like:
this.document = null;
Then some time later when you create a new object using this constructor, like so:
var myLead = new Lead(dbConnection);
You'll have the myLead.document property.
There are many other things wrong with your code, though. Why are you assuming that there is a global document variable with relevant data visible in your library when it's defined as {}? The code in that if statement at the end of your constructor should be run when the document property is set in your other file below, and should only expect this.document to exist.
You set var document = {} initially, and {} is not falsy. Better would be to set as a starting value document = null and then after checking for !document set document = {} before assigning whatever properties you need.