DEV Community

Cover image for 3 Ways to Upgrade your Mongoose Schemas
Daniel Troyano
Daniel Troyano

Posted on

3 Ways to Upgrade your Mongoose Schemas

So you've got your site set up with MongoDB, but you want some more functionality. So you install Mongoose, because you want a easy way to manage relationships between your data in MongoDB. Your site lets people upload Books and the Authors who write them. So your database looks a little like this.
Database Schema

It's not a complicated site, so you make some schemas and models using Mongoose that look like this.

const bookSchema = new mongoose.Schema({
  name: String,
  author: String,
});

const authorSchema = new mongoose.Schema({
  name: String,
});

const Author = mongoose.model('Author', authorSchema);
const Book = mongoose.model('Book', bookSchema);

Remember, Mongoose is going to create a unique _id property automatically whenever you add something to a table, so we don't need to declare it.

At this point we have two functioning tables. But they don't do a lot. If you want them to be linked, or have any cool functionality, you'd have to do it manually. But wouldn't you like to just call Book.create() with whatever you data is? Wouldn't that be nice? So let's use Mongoose's functionality to make our tables more powerful.


First, let's talk about using references and populate. Let's change our bookSchema to have a reference to our authorSchema.

const bookSchema = new mongoose.Schema({
  name: String,
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Author'
  },
});

Notice how we've changed the type to an objectId and pointed it at the Author model, so that the bookSchema knows which kind of model to populate the author field with. So now, as long as we make sure we're only putting valid Author ids in the author property on all of our Books, we can just chain a call to populate onto our finds, like this.

Book.find({}).populate('author');

We just have to tell populate what field we want to populate and it will automatically fill that field with the document it is referencing, kind of like a well written join in SQL!


Ok so now, let's say that we want to be able to track when users are adding these books to our database. But we don't want to add any code to our client side, so we have to handle it entirely with our database code.

First let's add another field to our growing schema.

const bookSchema = new mongoose.Schema({
  name: String,
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Author'
  },
  createdAt: Date
});

Did you know Mongoose has a Date type? It works pretty much just like a Javascript Date, and you can even use it to query ranges. If you want to know the specifics check out the Mongoose Docs.

So now that we have that let's add some middleware to our schema. Always make sure to add this before creating the model. There are several types of middleware that mongoose supports, but we're going to use 'save' which triggers when a new document is inserted into your database. If you want to check out the others, take a look at Mongoose's documentation.

bookSchema.pre('save', function(){
  this.createdAt = Date.now();
});

Notice our use of pre, this just means we want this to happen before we save the object. There is also 'post' middleware, which runs after the action has been done, in this case inserting the document into the table.

Then we affect any changes we want by using the keyword this to refer to the object that we are saving.

Great! Now we're adding a createdAt property to every Book that is tracking when they were added to the database.


Ok and finally I'm going to briefly touch on validators. Validators are just a shorthand way to add pre middleware to 'save' that return a boolean value. If that value is false, the document isn't added to the database, but if it returns true, everything is good and the document is inserted as expected.

Let's take a look at our Author schema and imagine that every time we add an author, we are also adding their phone number, and we need a valid phone number to add them to the database. So our new schema would look like this.

const authorSchema = new mongoose.Schema({
  name: String,
  phoneNumber: {
    type: String,
    validate: {
      validator: item => /\d{3}-\d{3}-\d{4}/.test(item),
      message: ({value}) => `${value} is not a valid phone number!`
    },
  }
});

To set up a validator, we define a validate property on our field. It needs a validator function to return a boolean that equates to the property's validity. And the message here is essentially the error message we are getting back. You can tailor this however you want, but using value like I did gives you access to the value that failed the validity test.


And that's it, we've touched on ways to give MongoDB some of the functionality of a SQL database and a few ways make schema more robust. And hopefully that's given you some ideas on how to upgrade your own Mongoose Schemas!

Top comments (1)

Collapse
 
monfernape profile image
Usman Khalil • Edited

I'd say I've basic understanding of mongoose and am only limited to CRUD at this point. But I'm very glad to see the built-in validators and functions such as pre and post. Please bring more on mongoose