svijay.aug12
svijay.aug12

Reputation: 561

Populate array of ObjectId's in response using node js

I am new to programming in NodeJS and was looking for solutions around how to populate response attributes in json Object which are of type Object Array.

Below is the sample response from my service -

{
        "Roles": [
            "5b7fd72537651320a03494f8",
            "5b7fdc06e3fcb037d016e26a"
        ],
        "_id": "5b8e530be2d4630fd4f4b34a",
        "Username": "ctucker",
        "FirstName": "Chris",
        "LastName": "Tucker",
        "Email": "[email protected]",
        "createdAt": "2018-09-04T09:40:27.091Z",
        "updatedAt": "2018-09-04T09:40:27.091Z",
        "__v": 0
    }

I would like to populate the ObjectId's in the Roles array attribute with the actual name instead of the Ids.

How should I be doing this.

Following is the way, I have defined the User and Roles schema

User Model

const userSchema = new mongoose.Schema({

    Username: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 255,
        unique: true
    },
    FirstName: {
        type: String
    },
    LastName: {
        type: String
    },
    Email: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 255
    },
    Password: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 1024
    },
    Roles: [{type: mongoose.Schema.Types.ObjectId, ref: Roles}],
    Active: {type: Boolean, default: true},
    SuperUser: {type: Boolean, default: false}
},{
    timestamps: true
});

Roles Model

const rolesSchema = new mongoose.Schema({
    RoleName: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 255,
        unique: true
    },
    Description: {
        type: String
    },
    Active: {type: Boolean, default: true}
}, {
    timestamps: true
});

Create User

router.post('/', [auth, admin], async (req, res) => {

  const { error } = validate(req.body); 
  if (error) return res.status(400).send(error.details[0].message);

  let user = await User.findOne({ Username: req.body.Username });
  if (user) return res.status(400).send('User with username: ', req.body.Username, 'already exists. Please try with any other username');

  let roleInput = [];

  if(req.body.Roles !== null) {
    req.body.Roles.forEach( async (element) => {
      objectId = mongoose.Types.ObjectId(element);
      roleInput.push(objectId);
    });
  }

  console.log('Role info ', roleInput);

  user = new User(_.pick(req.body, ['Username', 'FirstName', 'LastName' ,'Email', 'Password', 'Active','SuperUser']));

  console.log('User Input => ', user);

  const salt = await bcrypt.genSalt(10);
  user.Password = await bcrypt.hash(user.Password, salt);

  roleInput.forEach( async (objectId) => {
    user.Roles.push(objectId);
  });  

  console.log('User Input after role addition => ', user);

  await user.save();

  const token = user.generateAuthToken();
  res.header('x-auth-token', token).send(_.pick(user, ['_id', 'FirstName', 'LastName' ,'Email', 'Roles']));

});

I would like to get the RoleName instead of the ObjectId in response. Any help in this regard would be appreciated.

Forgot to mention that I already tried using the populate method, but I see the following exception.

How I am using the populate method -

router.get('/', auth, (req, res, next) => {
  var request_params = url.parse(req.url,true).query;
  var searchQuery = (request_params.mode === 'active') ? {'Active': true} : {};
  User.find(searchQuery)
    .populate('Roles')
    .select(['-Password', '-Active', '-SuperUser'])
    .then((users) => {
        res.send(users);
    }, (error) => {
      next(error);
    });
});

Exception -

<!DOCTYPE html>
<html>
    <head>
        <title></title>
        <link rel="stylesheet" href="/stylesheets/style.css">
    </head>
    <body>
        <h1>Schema hasn't been registered for model &quot;[object Object]&quot;.
Use mongoose.model(name, schema)</h1>
        <h2></h2>
        <pre>MissingSchemaError: Schema hasn't been registered for model &quot;[object Object]&quot;.
Use mongoose.model(name, schema)
    at new MissingSchemaError (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\lib\error\missingSchema.js:20:11)
    at NativeConnection.Connection.model (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\lib\connection.js:791:11)
    at getModelsMapForPopulate (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\lib\model.js:4016:20)
    at populate (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\lib\model.js:3505:21)
    at _populate (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\lib\model.js:3475:5)
    at utils.promiseOrCallback.cb (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\lib\model.js:3448:5)
    at Object.promiseOrCallback (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\lib\utils.js:232:14)
    at Function.Model.populate (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\lib\model.js:3447:16)
    at cb (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\lib\query.js:1678:17)
    at result (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\node_modules\mongodb\lib\utils.js:414:17)
    at executeCallback (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\node_modules\mongodb\lib\utils.js:406:9)
    at handleCallback (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\node_modules\mongodb\lib\utils.js:128:55)
    at cursor.close (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\node_modules\mongodb\lib\operations\cursor_ops.js:211:62)
    at handleCallback (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\node_modules\mongodb\lib\utils.js:128:55)
    at completeClose (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\node_modules\mongodb\lib\cursor.js:887:14)
    at Cursor.close (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\node_modules\mongodb\lib\cursor.js:906:10)</pre>
    </body>
</html>

Upvotes: 0

Views: 2943

Answers (3)

svijay.aug12
svijay.aug12

Reputation: 561

Understood the problem.

Within the User schema definition, I had to define the Roles Schema as well.

const rolesSchema = new mongoose.Schema({
    RoleName: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 255,
        unique: true
    },
    Description: {
        type: String
    },
    Active: {type: Boolean, default: true}
}, {
    timestamps: true
});

const Roles = mongoose.model('Roles', rolesSchema);

const userSchema = new mongoose.Schema({

    Username: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 255,
        unique: true
    },
    FirstName: {
        type: String
    },
    LastName: {
        type: String
    },
    Email: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 255
    },
    Password: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 1024
    },
    Roles: [{type: mongoose.Schema.Types.ObjectId, ref: 'Roles'}],
    Active: {type: Boolean, default: true},
    SuperUser: {type: Boolean, default: false}
},{
    timestamps: true
});

After adding the roles schema definition within the user schema, the response from postman does populate the RoleName in response.

{
        "Roles": [
            {
                "RoleName": "AC-User"
            },
            {
                "RoleName": "AC-PMLead"
            }
        ],
        "_id": "5b8e48e2f3372a098c6e5346",
        "Username": "mcmillan",
        "FirstName": "McMillan",
        "LastName": "McMillan",
        "Email": "[email protected]",
        "createdAt": "2018-09-04T08:57:07.029Z",
        "updatedAt": "2018-09-04T08:57:07.029Z",
        "__v": 0
    }

Upvotes: 1

Sridhar
Sridhar

Reputation: 11786

You can use Mongoose's populate to populate RoleName instead of ObjectId refs

Mongoose has a more powerful alternative called populate(), which lets you reference documents in other collections.

'use strict';

let user = await User.findOne({
  Username: req.body.Username
}).populate('Roles');

if (user && user.Roles.length > 0) {
  for (let role of user.Roles) {
    console.log(role.RoleName);// logs `RoleName`
  }
}

Upvotes: 0

Bharathvaj Ganesan
Bharathvaj Ganesan

Reputation: 3194

Simple using mongoose populate() function

const user = await User.findOne().populate('Roles');
// output => user.Roles // will be array of mongo documents

Cheers

Upvotes: 0

Related Questions