Search code examples
mongodbexpressmongooseejsmongoose-populate

How can I access the properties of a populated() database query result in ejs?


I am trying to access and display the results of a populated database query on the front-end, but I get undefined when I try to access properties of the populated variable. So below are the schemas that my mongoose find method is referencing.

Quote Schema

const express = require('express')
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const User = require('../models/users')

let QuoteSchema = new Schema({
    author: {
        type: String,
        trim: true
    },
    quote: {
        type: String,
        unique: [true, "this quote already exists."],
        trim: true
    },
    submittedBy: [{
        type: Schema.Types.ObjectId,
        ref: 'users'
        // trim: true,
        // required: true
    }]
}, {
    timestamps: true
})

const Quote = mongoose.model('quotes', QuoteSchema)
module.exports = Quote

User Schema

const express = require('express')
const mongoose = require('mongoose')
const Schema = mongoose.Schema

let UserSchema = new Schema ({
    username: {
        type: String,
        unique: [true, "This username is taken."],
        trim: true,
    },
    email: {
        type: String,
        unique: [true, "This email is already being used."],
        trim: true
    },
    password: {
        type: String,
        trim: true
    },
    favorites: []
}, {
    timestamps: true,
})

const User = mongoose.model('users', UserSchema)
module.exports = User

Here is the mongoose query. The populate method is appended to the find method, and the sort method is appended to the populate method. The typeof of the results confirms that it is an object, but I don't think it remains that way when it gets to ejs.

Database query and post request in Route file

router.get('/', (req, res, next) => {
    console.log("beginning query...")
    const flashMessages = res.locals.getMessages()
    if (req.user) {
        Quote.find({}).populate('submittedBy').sort({ _id: -1 })
        .exec((err, results) => {
            console.log(typeof results) // returns "object"
            err ? console.log(err) : res.render('index', { quotes: results, user: req.user, csrfToken: req.csrfToken() });
        })
    }
})


router.post('/quotes', (req, res, next) => {
    if(req.isAuthenticated()) {
        let quote = new Quote({
            author: req.body.author,
            quote: req.body.quote,
            submittedBy: req.body.userId
        })

        quote.save((err, quote) => {
            if (err) {
                console.log(err)
            } else {
                console.log("Added the quote \"" + quote.quote + "\" by " + quote.author + ".")
                res.redirect('/')
            }
        })
        .catch(next)
    } else {
        req.flash("restricted", "You must first login before you can add quotes to the community feed.")
        res.redirect('/')
    }
})

This is where I think the error is occurring. On the very last span tag, quotes[i].submittedBy returns the entire "submittedBy" variable, which means the population method did what it was supposed to. But if I try to access a property on that object, it returns undefined. quotes[i].submittedBy.username returns undefined. When I inspect the element, the submittedBy is encased by quotations, so I think it is a string now for some reason. But if I try to use either JSON.parse() or JSON.stringify(), I get an error in both cases. I am not sure what to do. Please help!

Ejs render

<% for(var i = 0; i < quotes.length; i++) {%>
    <li class="quote">
        <i class="fa fa-close fa-quote-close move hidden"></i>
        <i class="fa fa-heart hidden"></i>
        <span class="make-bold nhauthorValues" id="<%= quotes[i]._id %>" contenteditable="true"><%= quotes[i].author %></span> once said: "<span class="quoteValues" id="<%= quotes[i]._id %>" contenteditable="true"><%= quotes[i].quote %></span>"
        <span class="by"><%= quotes[i].submittedBy.username %></span>
    </li>
<% } %>

Solution

  • Probably There Is An Error In The Way You Are Inserting Instances:

    let quote = new Quote({
       author: req.body.author,
       quote: req.body.quote,
       {$push:{submittedBy: req.body.userId}}
    });
    

    Try Using This Maybe This Will Work, If It Doesn't Give the snapshot of the object you are getting after populating the result before rendering.

    you will get an array of submittedBy so you cannot directly access username, because in the schema you have given array type.

    use instead curly braces instead of square brackets.

    you can do like:

    const express = require('express')
    const mongoose = require('mongoose') 
    const Schema = mongoose.Schema
    const User = require('../models/users')
    
    let QuoteSchema = new Schema({
     author: {
         type: String,
         trim: true
     },
     quote: {
         type: String,
         unique: [true, "this quote already exists."],
         trim: true
     },
     submittedBy: {
         type: Schema.Types.ObjectId,
         ref: 'users'
         // trim: true,
         // required: true
     } //removed square brackets
     }, {
    timestamps: true
    })
    
    const Quote = mongoose.model('quotes', QuoteSchema)
    module.exports = Quote
    

    Now follow the same procedure you were following ignore the first procedure in the answer if you have only one user submitting the quote. If multiple users can then submittedBy will be an array and access it accordingly.