Skip to main content

Advanced Mongoose

Lesson Objectives

  1. Use helper functions to make your life easier
  2. Extend the abilities of models
  3. Extend the abilities of classes
  4. Create a schema for properties of models that are objects
  5. Reference other models by id
  6. Create actions for models to execute during database actions
  7. Use indexes to speed up searches

Use helper functions to make your life easier

Mongoose's default find gives you an array of objects. But what if you know you only want one object? These convenience methods just give you one object without the usual array surrounding it.

try {
const findArticleByID = await Article.findById("5757191bce5579b805705900");
console.log(findArticleByID);
} catch (error) {
console.log(error);
};
try {
const findArticle = await Article.findOne({ author: "Matt" });
console.log(findArticle);
} catch (error) {
console.log(error);
};
try {
const updatedArticle = await Article.findByIdAndUpdate(
"5757191bce5579b805705900", // id of what to update
{ $set: { author: "Matthew" } }, // how to update it
{ new: true }); // tells findOneAndUpdate to return modified article, not the original
console.log(updatedArticle);
} catch (error) {
console.log(error);
};
try {
const updatedArticle = await Article.findOneAndUpdate(
{ author: "Matt" }, // search criteria of what to update
{ $set: { author: "Matthew" } }, // how to update it
{ new: true }); // tells findOneAndUpdate to return modified article, not the original
console.log(updatedArticle);
} catch (error) {
console.log(error);
};
try {
const removedArticle = await Article.findByIdAndRemove("5757191bce5579b805705900");
console.log(removedArticle); // log article that was removed
} catch (error) {
console.log(error);
};
try {
const removedArticle = await Article.findOneAndRemove({ author: "Matt" });
console.log(removedArticle);
} catch (error) {
console.log(error);
};

Extend the abilities of models

You can give all models using a specific schema extra properties and methods when creating that schema

// after initial schema declaration
articleSchema.methods.longTitle = () => {
return this.author + ": " + this.title;
};

// instantiate a model and then call it
const article = new Article();
console.log(article.longTitle());

Extend the abilities of classes

You can create static methods for model constructor functions in their schema

// after initial schema declaration
articleSchema.statics.search = function (name, cb) {
// because we use this here, we'll need an old-fashioned function
// In this situation, it's like .find(), but it searches both title and author
return this.find(
{
$or: [
{ title: new RegExp(name, "i") },
{ author: new RegExp(name, "i") },
],
},
cb
);
};

// call the static method
Article.search("Some", (err, data) => {
console.log(data);
});

Reference other models by id

Sub docs are difficult when it comes to updating, because all duplicates must be updated. But we can reference by ID to get around this.

Single ObjectId reference

const mongoose = require("mongoose");
const Schema = mongoose.Schema;
mongoose.connect("mongodb://localhost:27017/test");

const articleSchema = new Schema({
title: { type: String },
author: { type: Schema.Types.ObjectId, ref: "Author" }, //the author property is just an id of another object
});

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

const Article = mongoose.model("Article", articleSchema);
const Author = mongoose.model("Author", authorSchema);

const matt = new Author({ name: "Matt" });
matt.save(() => {
const article1 = new Article({ title: "Awesome Title", author: matt._id });
article1.save(() => {
showAll();
});
});

const showAll = async () => {
try {
const populatedArticle = await Article.find()
.populate("author");
console.log(populatedArticle);
} catch (error) {
console.log(error);
};
};

Arrays of ObjectId references

We can do the same with arrays of ids

const authorSchema = new Schema({
name: { type: String },
articles: [{ type: Schema.Types.ObjectId, ref: "Articles" }],
});

const Article = mongoose.model("Article", articleSchema);
const Author = mongoose.model("Author", authorSchema);

const matt = new Author({ name: "Matt" });
let article_id;
matt.save(() => {
let article1 = new Article({ title: "Awesome Title", author: matt._id });
article1.save(() => {
article_id = article1._id;
matt.articles.push(article1);
matt.save(showAll);
});
});

let showAll = async () => {
try {
const populatedAuthor = await Author.find()
.populate("articles");
console.log(populatedAuthor);
} catch (error) {
console.log(error);
};
};

Create actions for models to execute during database actions

We can perform actions before and after database work

articleSchema.pre("save", function (next) {
//because we use this here, we'll need an old-fashioned function
console.log(this);
console.log("saving to backup database");
next();
});

async

articleSchema.pre("save", true, function (next) {
//because we use this here, we'll need an old-fashioned function
console.log(this);
console.log("saving to backup database");
next();
doAsync(done);
});

post

articleSchema.post("save", function (next) {
//because we use this here, we'll need an old-fashioned function
console.log("saving complete");
});