Populate array of ObjectId's in response using node js - 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": "ctucker#innovate.com",
"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 "[object Object]".
Use mongoose.model(name, schema)</h1>
<h2></h2>
<pre>MissingSchemaError: Schema hasn't been registered for model "[object Object]".
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>

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": "mcmillan#innovate.com",
"createdAt": "2018-09-04T08:57:07.029Z",
"updatedAt": "2018-09-04T08:57:07.029Z",
"__v": 0
}

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

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`
}
}

Related

findByIdAndUpdate overwrites existing value

facing a small issue that whenever I try to update a 'comments' property it automatically overwrites old property, doesn't add next value to the array. Tried many options like adding $set parameter as option, removing it, adding overwrite: false, but not successfully. Been looking at the docs, but feels like I'm something missing and even docs can't help me.
My Model:
const mongoose = require("mongoose");
const TicketSchema = new mongoose.Schema({
title: {
type: String,
minlength: 5,
maxlength: 50,
required: [true, "Please validate title"]
},
description: {
type: String,
minlength: 10,
maxlength: 200,
required: [true, "Please validate description"]
},
comments: {
type: Array,
default: []
},
status: {
type: String,
enum: ["in progress", "resolved", "pending"],
default: "pending",
},
priority: {
type: String,
enum: ["regular", "medium", "high"],
default: "regular"
},
createdBy: {
type: mongoose.Types.ObjectId,
ref: "User",
required: [true, "Please provide user"],
}
}, {
timestamps: true,
});
module.exports = mongoose.model('Ticket', TicketSchema);
My controller:
const updateUser = async (req, res) => {
const {
body: {username, email, firstname, lastname, country, password, comments},
params: {
id: employeeId
}
} = req;
const user = await User.findByIdAndUpdate(employeeId, req.body, {
$set: {
comments: req.body.comments
}
}, {
new: true, runValidators: true, upsert: true
}).select(["-password"]);
// Don't allow IT users to change credentials for IT and ADM users.
if (user.type.startsWith("IT") || user.type.startsWith("ADM")) throw new NotFoundError(`No user with id ${employeeId}`);
if (!user) throw new NotFoundError(`No user with id ${employeeId}`);
res.status(StatusCodes.OK).json({user});
};
You may want to use $push operator instead of $set.
Assuming req.body.comments is an array with comments, you could use $each to construct something like this:
const user = await User.findByIdAndUpdate(employeeId, req.body, {
$push: {
comments: { $each: req.body.comments }
}, {
new: true, runValidators: true, upsert: true
}
}).select(["-password"]);

Cannot Populate path in Mongoose while trying to join two documents

MongooseError: Cannot populate path loaned_to because it is not in your schema. Set the strictPopulate option to false to override.
I've tried to join two documents in mongodb using mongoose in nodejs, But unfortunately this error occurs. My mongoose version is 6.0.6
Book Schema
const mongoose = require('mongoose');
const BookSchema = new mongoose.Schema({
"name": {type: String, required: true},
"author_name": {type: String, required: true},
"published_date": {type: Date, required: false},
"copies": [
{
"isbn_number": {type: String, required: true},
"status": {type: String, required: true, default: "Available"},
"due_back": {type: Date, required: false},
"loaned_to": {type: mongoose.Schema.Types.ObjectId, required: false, ref: "User"}
},
]
})
const Book = mongoose.model("Book", BookSchema);
module.exports = Book;
User Schema
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
"first_name": {type: String, required: true},
"last_name": {type: String, required: true},
"phone_number": {type: String, required: true},
"address": {type: String, required: false},
"user_name":{type: String, required: true},
"password": {type: String, required: true},
"email": {type: String, required: true},
"notifications": [
{
"notification_id" : {type:"string", required:true},
"notification": {type: "string", required: true}
},
]
})
const User = mongoose.model("User", UserSchema);
module.exports = User;
My code to join documents
exports.getAllBooks = async (req, res) => {
try {
let data = await BookModel.findOne().populate("loaned_to");
res.status(200).send({data: [...data], success: true})
} catch (err) {
console.log(err)
res.status(404).send({success: false, msg: err.message})
}
}
exports.getAllBooks = async (req, res) => {
try {
let data = await BookModel.findOne().populate({
path: 'copies.loaned_to',
select:
'first_name lastName phone_number address user_name email notifications',
});
res.status(200).json({ data: [...data], success: true });
} catch (err) {
console.log(err);
res.status(500).json({ success: false, msg: err.message });
}
};
Use nested populate as in the example below(The example assumes that a Token model has a user which in tern has a role and a role has permissions).
This will return a user object associated with the filtered token, with the role the user is assigned to and the permissions assigned to the role.
That is: TokenModel (has relationship to) -> UserModel (has relationship to) -> RoleModel (has relationship to) -> PermissionsModel)
const popObj = {
path: 'user',
options: { sort: { position: -1 } },
populate: {
path: 'role',
select: 'name',
populate: {
path: 'permissions'
}
}
};
const tokenFilter = {is_active: true};
TokenModel.find(userFilter).populate(popObj);

POPULATION ISSUE: Mongoose/Express

I'm trying to have each record attached to a user who created it,
and every user have their records attached.
Here are my schemas:
1.The Records schema:
const mongoose = require('mongoose')
const RecordsSchema = new mongoose.Schema(
{
Title: { type: String, required: true },
postedby:[{
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
}],
Author: { type: String, required: true},
ISBN: { type: String, required: true },
Review: { type: String },
SelectedFile: { type: String },
Likes: { type: Number, default:0},
Date: { type: Date, default: Date.now()}
});
module.exports = Records = mongoose.model('record', RecordsSchema', 'record');`
Here is the The user Schema:
const mongoose = require('mongoose')
const userSchema = new mongoose.Schema(
{
username: { type: String},
email: { type: String, required: true ,unique:true},
records:[{
type: [mongoose.Schema.Types.ObjectId],
ref: 'record' }],
password: { type: String, required: true},
Date: { type: Date, default: Date.now(), immutable: true }
});
module.exports = User = mongoose.model('user', userSchema,'user');
The express route for getting a record:
router.get('/postedby/', (req, res) => {
Records.find(req.params.id)
.populate('postedby')
.exec()
.then(post =>{
if (!post) {
return res.status(400).json({ msg: 'Add Posts' });
}
else return res.json(post);
}).catch (err => console.error(err))
});
Result of the route:
{
"postedby": [],
"Likes": 0,
"_id": "5fed8c12a4fb2c1e98ef09f6",
"Title": "New Age",
"Author": "Situma Prisco",
"ISBN": "23422",
"SelectedFile": "",
"Review": "",
"Date": "2020-12-31T08:30:10.321Z",
"__v": 0
},
I'm getting a blank Array on the populated user field(posteddby) .
Please help, What am I doing wrong? And yes, i do have a User Logged in
You are too close.
In your schema definition, postedby field itself is an array. Hence you can simply define it like :
postedby:[{
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
}]
Make sure that the ObjectID is stored properly in the postedby array.
Also, you are trying to find a single record based on the ID, hence you can use findById(req.params.id) or findOne({_id:req.params.id}) which would return a single document.

Mongoose insertion order

I am a very beginner at APIs. I am trying to create a test API for my upcoming website but I am stuck at some point. I used SQL, and I am used to orders when inserting data into the database. While using MongoDB and the mongoose library at insertion the pre-made Schema's order lost after inserting data.
This is my model file for the user.
const mongoose = require("mongoose")
const UserSchema = mongoose.Schema({
username: {
type: String,
require: true,
minlength: 6,
maxlength: 64
},
password: {
type: String,
require: true,
minlength: 6,
maxlength: 255
},
email: {
verified: {type: Boolean, default: false},
type: String,
require: true,
minlength: 11,
maxlength: 64
},
description: {
type: String,
require: true,
minlength: 20,
maxlength: 5000
},
permissions: {
verified: {type: Boolean, default: false, require:true},
admin: {type: Boolean, default: false, require:true},
server: {type: Number, default: 0, require:true}
},
image: {
type: String,
require: true,
default: ""
},
reg_date: {
type: Date,
default: Date.now(),
require: true
}
});
module.exports = mongoose.model("User", UserSchema);
The body of the POST request:
{
"username": "nameblabla",
"email": "email#email.com",
"description": "akjfhajkléfhakaklsfhak-sfn-ak.sfnkl-asfnkléas-f",
"password": "pasfgasgasdg"
}
And the file of the POST request:
const express = require('express')
const router = express.Router();
const userModel = require('../models/User')
//VALIDATION
const joi = require('#hapi/joi');
const schema = joi.object({
username: joi.string().min(6).required(),
email: joi.string().min(6).required().email(),
description: joi.string().min(20).required(),
password: joi.string().min(6).required()
});
router.post('/signup', async (req,res) => {
//VALIDATE
const {error} = schema.validate(req.body, {abortEarly: false});
if(error){
let msg = ""
error.details.forEach(e => {
msg += e.message + "\n";
});
return res.status(400).send(msg)
}else{
const user = new userModel({
username: req.body.username,
email: req.body.email,
description: req.body.description,
password: req.body.password
});
try{
const savedUser = await user.save();
res.send(savedUser)
}catch(err) {
res.status(400).send(err);
}
}
})
module.exports = router
The inserted data looks like:
{
"permissions": {
"verified": false,
"admin": false,
"server": 0
},
"image": "",
"reg_date": "2020-04-06T14:50:02.910Z",
"_id": "5e8b41a0cddf023104955793",
"username": "Miraglia1",
"email": "email#email.com",
"description": "akjfhajkléfhakaklsfhak-sfn-ak.sfnkl-asfnkléas-f",
"password": "pasfgasgasdg",
"__v": 0
}
So when I save the data the default key:value pairs placed above the submitted data. But is there any solution to keep the Schema order? Please help me.
Thanks in advance. :)

Referencing Object Id not working in Mongoose 4.11.6

I have this problem. Basically, I have 2 schemas - a User schema and a Document schema. The Document schema has an owner which references the _id field of documents in the User collection.
The problem is that I am still able to save documents in the Document collection with owner ids that do not exist in the User collection which should not be so.
Here is my User schema and Document schema respectively
const UserSchema = new Schema({
firstName: {
type: String,
required: true,
},
lastName: {
type: String,
required: true,
},
email: {
type: String,
validate: [{ validator: value => isEmail(value), msg: 'Invalid email.'
}],
unique: true,
required: true,
},
password: {
type: String,
required: true,
},
isAdmin: {
type: Boolean,
default: false,
},
}, {
timestamps: true,
});
const User = mongoose.model('User', UserSchema);
And the Document Schema
const DocumentSchema = new Schema({
title: {
type: String,
required: true,
},
text: {
type: String,
},
access: {
type: String,
enum: ['public', 'private'],
default: 'public',
},
owner: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true,
},
}, {
timestamps: true,
});
const Document = mongoose.model('Document', DocumentSchema);
Any help will be appreciated thanks.
For that situation you can add pre save function in your Document schema that will call before save your Document.
const DocumentSchema = new Schema({
// ...
}, {
timestamps: true,
});
DocumentSchema .pre("save",function(next) {
var self = this;
if (self.owner) {
mongoose.models['User'].findOne({_id : self.owner }, function(err, existUser){
if(err){
return next(false, err);
}
if(!existUser)
return next(false, "Invalid user reference");
else
return next(true);
});
} else {
next(false, "Owner is required");
}
});
const Document = mongoose.model('Document', DocumentSchema);

Resources