Cannot set properties of undefined (setting 'login')

Viewed 399

It looks like it doesn't like how I set the nested schema?

User Schema

const mongoose = require("mongoose");
    const twitchSchema = mongoose.Schema(
      {
        id: Number,
        login: String,
        display_name: String,
        type: String,
        broadcaster_type: String,
        description: String,
        profile_image_url: String,
        offline_image_url: String,
        view_count: Number,
        email: String,
        created_at: String,
        provider: String,
        accessToken: String,
        refreshToken: String,
      },
      { _id: false }
    );
    const userSchema = new mongoose.Schema({
      provider: {
        youtube: {},
        twitch: twitchSchema,
      },
    });
    module.exports = mongoose.model("User", userSchema);

This is how I create a new document

const userModels = new UserModels();
userModels.provider[profile.provider].login = profile.login;
const result = await userModels.save()
5 Answers

You should always add the error you are getting in your question. I have tried your code and got undefined reference error:

// Schema setup

const mongoose = require('mongoose');
const twitchSchema = mongoose.Schema(
    {
        id: Number,
        login: String,
        display_name: String,
        type: String,
        broadcaster_type: String,
        description: String,
        profile_image_url: String,
        offline_image_url: String,
        view_count: Number,
        email: String,
        created_at: String,
        provider: String,
        accessToken: String,
        refreshToken: String,
    },
    { _id: false }
);
const userSchema = new mongoose.Schema({
    provider: {
        youtube: {},
        twitch: twitchSchema,
    },
});

module.export = mongoose.model('User', userSchema);

// Driver code
const u = new User();
console.log(u);

const profile = {
  provider: 'twitch',
  login: 'dummy_login',
};

u.provider[profile.provider].login = profile.login;
console.log(u);

const res = await User.create(u);
console.info(res);

// Error
// { _id: new ObjectId("624e8901dc46c217a1461c41") }
// TypeError: Cannot set property 'login' of undefined

So the problem is while setting the login field in the user model. You should tweak your userSchema like this:

const userSchema = new mongoose.Schema({
    provider: {
        youtube: {},
        twitch: { type: twitchSchema, default: {} },
    },
});

Please see mongoose documentation here. Now if you run the below driver code it is able to create the record in mongo.

const u = new User();
console.log(u);

const profile = {
  provider: 'twitch',
  login: 'dummy_login',
};

u.provider[profile.provider].login = profile.login;
console.log(u);

const res = await User.create(u);
console.info(res);

See logs below:

{ _id: new ObjectId("624e8a9990d90dea47e7f62a") }
{
  provider: { twitch: { login: 'dummy_login' } },
  _id: new ObjectId("624e8a9990d90dea47e7f62a")
}
{
  provider: { twitch: { login: 'dummy_login' } },
  _id: new ObjectId("624e8a9990d90dea47e7f62a"),
  __v: 0
}

user is created in mongo

You are using mongoose.Schema incorrect. This is called embeded documents in mongodb. You can define schema like following:

const mongoose = require('mongoose');
const { Schema } = mongoose;

const twitchSchema = new Schema({
    id: Number,
    login: String,
    display_name: String,
    type: String,
    broadcaster_type: String,
    description: String,
    profile_image_url: String,
    offline_image_url: String,
    view_count: Number,
    email: String,
    created_at: String,
    provider: String,
    accessToken: String,
    refreshToken: String,
});

const userSchema = new Schema({
provider: {
    youtube: {},
    twitch: [twitchSchema],
  },
});

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

Try defining user schema like this :-

const userSchema = new mongoose.Schema({
  provider: {
    youtube: {},
    twitch: [twitchSchema],
  },
});

There are a couple issues:

  • your schema definition has extra commas, missing 'new', and curly brackets, and an additional _id object just hanging out there.
  • youtube: {}, is not a valid schema type, should be youtube: Object for example, or define a schema for it! (see my next bullet)
  • You can't nest one schema within another like this, see comment from Bhaskar. I'd suggest you define TwitchSchema and a YoutubeSchema separately, and combine them in a third UserSchema.
  • If you omit fields at doc creation ( only provide 'login' and omit others) the resulting document WILL NOT have any other fields. If that is not what you want, provide at least default empty values for all other fields as well.
const mongoose = require('mongoose');
main().catch(err => console.log(err));
async function main() {
  await mongoose.connect('mongodb+srv://ADDYOURS');  // <--- add url
  const TwitchSchema = new mongoose.Schema({
    id: Number,
    login: String,
    display_name: String,
    type: String,
    broadcaster_type: String,
    description: String,
    profile_image_url: String,
    offline_image_url: String,
    view_count: Number,
    email: String,
    created_at: String,
    provider: String,
    accessToken: String,
    refreshToken: String
  });
  const YoutubeSchema = new mongoose.Schema({
    id: Number,
    login: String
  });
  const userSchema = new mongoose.Schema({
    id: Number,
    twitch : [TwitchSchema],
    youtube : [YoutubeSchema]
  });

  UserModels = mongoose.model("User", userSchema);

  const UserDocument = new UserModels();

  let profile = {
    provider : 'twitch',
    login : 'foobars'
  }

  UserDocument[profile.provider].push({ login : profile.login});

  console.log( UserDocument[profile.provider]);
  const result = await UserDocument.save()
}

The above results in the following document:

enter image description here

If I were you I would do something like this

Define Schemas

const TwitchSchema = new mongoose.Schema({
    twitchID: Number,
    login: String,
    display_name: String,
    type: String,
    broadcaster_type: String,
    description: String,
    profile_image_url: String,
    offline_image_url: String,
    view_count: Number,
    email: String,
    created_at: String,
    provider: String,
    accessToken: String,
    refreshToken: String
});

const YoutubeSchema = new mongoose.Schema({
    youtubeID: Number,
    login: String
});


const UserSchema = new mongoose.Schema({
    twitch: {
        type: Schema.Types.ObjectId,
        ref: 'Twitch'
    },
    youtube: {
        type: Schema.Types.ObjectId,
        ref: 'Youtube'
    }
});

const User = mongoose.model('User', UserSchema);
const Twitch = mongoose.model('Twitch', TwitchSchema);
const Youtube = mongoose.model('Youtube', YoutubeSchema);

After this when I want to save the user and twitch value. I would do

route.post('/fake-route', async (req, res) => {
    try {
        //let's assume I have all the value from twitch in the `twitchData` variable
        let twitchData = getTwitchData();

        // Save Twitch Data to Mongo
        const twitchSaveToDB = await Twitch.create(twitchData);

        //Now Save User data using above saved Twitch Data
        let userDataToSave = {
            twitch: twitchSaveToDB._id
        };
        const userToDB = await User.create(userDataToSave);

        res.status(201).json({
            message: 'User Created'
        });
    } catch (err) {
        console.error(err);
        res.status(500).json({
            message: 'Error Occured'
        });
    }

})

I didn't test the code exactly, so there might be few things to fix here and there but this would be the general concept I would follow

Related