fugu
fugu

Reputation: 6568

Associate two mongoose databases in express app

I have two mongoose DB schema, a User and a Profile:

var userSchema = new mongoose.Schema({
    username: String,
    password: String,
    type:     String
});

userSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("User", userSchema);

var profileSchema = new mongoose.Schema({
    username: { 
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User'
    },
    email: { 
        type: String,
        default: ""
    },
    skill: { 
        type: String,
        default: ""
    }
});

module.exports = mongoose.model("Profile", profileSchema);

I am first registering and authenticating a user using passport-js, and if this is successful, I then want to create populate the Profile db with username and type. I will then allow users to update this Profile db (but not the Users). Following the docs I am trying to use .populate, to create an entry in Profile with the same information as in User:

router.post("/signup", function(req, res) {
    var newUser = new User({username: req.body.username, type: req.body.type});

    User.register(newUser, req.body.password, function(err, user){
        if(err){
            console.log(err.message);
            return res.render("signup");
        }
        passport.authenticate("local")(req, res, function(){
            Profile.findById(newUser._id).populate("username").exec(function(err, foundUser){
                if(err){
                    console.log("Problem populating the profiles DB");
                }
                else{
                    console.log("New user:" + newUser._id);
                    res.redirect("profile/" + newUser._id);
                }
            });
        });
    });
});

This passes without error, but my Profiles db is empty.

Can anyone see what I'm doing wrong here?

I have also tried (within the same route):

Profile.create(newUser, function(err, newlyCreated){
    if(err){
        console.log(err);
    } else {
        console.log("Added user to profile db:" + newUser._id);
        console.log("New user:" + newUser._id);
        res.redirect("profile/" + newUser._id);
     }
 });

But this fails with error:

{ DocumentNotFoundError: No document found for query "{ _id: 5c6d9512a4a301b73b565681 }"

Another approach I'm trying is this:

Profile.create({username:req.body.username, type: req.body.type}, function(err, pro){
  if(err){
      console.log("Profile error: " + err);
  } else {
      console.log("Added user to profile db:" + newUser._id);
      console.log("New user:" + newUser._id);
      res.redirect("profile/" + newUser._id);
  }
});

Which passes, but produces an entry in Profiles with an _id different from the corresponding entry in Users.

Would anyone be able to point me in the right direction?

Upvotes: 0

Views: 248

Answers (1)

chab42
chab42

Reputation: 36

Several parts of your code are problematic.

You called newUser._id however newUser is an instantiation of the mongoose model User. The _id field is created by MongoDB when storing a document to the db, so it's logical the field isn't filled when just instantiating an object. It is created when saving it to the db. So you can access the correct _id on user._id, with user coming from the callback.

router.post("/signup", function(req, res) {
    var newUser = new User({username: req.body.username, type: req.body.type});

    User.register(newUser, req.body.password, function(err, user){
        if(err){
            console.log(err.message);
            return res.render("signup");
        }
        passport.authenticate("local")(req, res, function(){
            Profile.findById(user._id).populate("username").exec(function(err, foundUser){
                if(err){
                    console.log("Problem populating the profiles DB");
                }
                else{
                    console.log("New user:" + user._id);
                    res.redirect("profile/" + user._id);
                }
            });
        });
    });
});

Moreover you have strange parts in your code:

  • You don't create a new profile in your profiles collection when creating a user. So how do you think it can find an document which wasn't created.
  • You're calling .findById on Profile and passing it an _id from a User document, whereas it's not an _id of this db, so there is no document with the queried _id in this collection.
  • You should query for Profile.findOne({username: user.name}) and then try to populate on your field: Profile.findOne({username: user.name}).populate('user') with profile.user = user._id when storing the profile.
  • Moreover, if you can have User username conflicts, then you should use the user._id to find the related documents, so store a unique value to your profile to find it easily.

So your schema should look a bit different:

var profileSchema = new mongoose.Schema({
    user: { 
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User'
    },
    email: { 
        type: String,
        default: ""
    },
    skill: { 
        type: String,
        default: ""
    }
});

and when populated through this call Profile.findOne({user: user._id}).populate('user') as a result you'll get:

{
    user: {
        username,
        password,
        type
    },
    email,
    skill
}

Remember:

  • You cannot assign a string to mongoose.Schema.Types.ObjectId,
  • Do not forget to create a Profile when creating a user, and reference the user._id inside your profile.user attribute.
  • Collection.findById(id) will look for a document inside the collection with document._id === id
  • If you want to populate on a field which is not typed as an objectId you have to define a virtual. Mongoose doc explains well this: https://mongoosejs.com/docs/populate.html#populate-virtuals
  • If you don't want to send the password when populating, you can restrict the fields: Profile.findOne({user user._id}).populate({path: 'user', select: "-password"}) . This request will remove the password field to reduce data leakage risks.

Upvotes: 1

Related Questions