loading...
Cover image for Virtuals in mongoose

Virtuals in mongoose

oviecodes profile image Godwin Alexander ・3 min read

What are Virtuals?

We may wish to have certain properties that we can call on our documents but don't want to save those properties in the database.
Examples of such properties are properties that

  • get the full name of a user,
  • get the number of a comments from the comments array of a user.

These properties are usually not required when the document is created, but occur as a result of some sort of processing carried out on the document.
As stated in the mongoose documentation, Virtuals are document properties that you can get and set but that do not get persisted to MongoDB.
Let's get to creating some virtuals of our own, Its an easy process in mongoose.
Suppose we have the following user model,

const userSchema = new mongoose.Schema({
    firstname: {
        type: String,
        required: true
    },
    lastname: {
        type: String
    }
})

const User = mongoose.model('user', userSchema)

and we want access to a user's full name (i.e firstname + lastname). We might do something like

const user = await User.create({
     firstname: 'Godwin',
     lastname: 'Alexander'
})

console.log(`${user.firstname} ${user.lastname}`) 

This would suffice for an operation that's as simple as getting the full name of a user, imagine if we had to carry out more sophisticated processing on the data model that requires 10+ lines of code in realtime. Having to write those 10+ lines of code over and over again becomes boring and cumbersome, thankfully there's a way out. All you have to do is declare a virtual in the data model as follows

userSchema.virtual('fullname')
.get(function() {
    return `${this.firstname} ${this.lastname}`
})

This logic creates a virtual field on the schema called fullname which returns a string containing the firstname and lastname.
The fullname virtual is not stored in Mongodb, rather it is created during runtime and is attached to model.

const user = await User.create({
        firstname: 'money',
        lastname: 'man'
    })
console.log(`Welcome ${user.fullname}`) // money man

As we can see from the above logic, virtuals also help with abstraction.

Warning: Since virtuals aren't stored in mongodb, you can't query with them. Doing something like this will fail

const users = await User.findOne({ fullname: 'Godwin Alexander' })

Apart from the get method on virtuals, a set method is also available. Setters are useful for de-composing a single value into multiple values for storage.
Suppose we update our userSchema to look like the following code

const userSchema = new mongoose.Schema({
    firstname: {
        type: String,
        required: true
    },
    lastname: {
        type: String
    },
    initials: {
        type: String
    }
})

Where initials is simply a string made up of the first letter of a user's first name and the first letter of the last name. We don't want to ask user to provide their initials, of course not. Mongoose provides a way to set such values, as long as they are specified in the schema.

userSchema.virtual('setInitials')
    .set(function(name) {
        const [first, last] = name.split(' ')
        this.initials = `${first[0]}${last[0]}`.toUpperCase()
    })

Now, we can do this

const user = await User.create({
        firstname: 'mayweather',
        lastname: 'floyd'
    })

user.setInitials = user.fullname
console.log(user.initials) //MF

That's all for this tutorial, the mongoose documentation however could give you more insight into virtuals and other amazing things which could be done with mongoose and Mongodb. See you in the next article.

Posted on by:

oviecodes profile

Godwin Alexander

@oviecodes

hi, i'm Godwin Alexander, a software engineer. I love building cool stuffs on the backend. An opensource enthusiast.

Discussion

pic
Editor guide