Mongoose is an object data modeling (ODM) tool for MongoDB and Node.js. It is designed to work in an asynchronous environment and supports both promises and callbacks.

Mongoose has all set of features that you need to model your application data. It includes built-in type casting, schema validation, query building, business logic hooks, and relationship management between data.

Before we get started with Mongoose, make sure that you have already installed and configured both Node.js and MongoDB (instructions for Mac and Ubuntu).

Installing Mongoose

You can install Mongoose in your Node.js project with the following command:

$ npm install mongoose --save

Now you can require Mongoose in your application:

const mongoose = require('mongoose');

Connecting to MongoDB

Connecting to database is incredibly simple with Mongoose. For applications that use only one database, Mongoose provides the connect() method:

const mongoose = require('mongoose');

const server = '127.0.0.1:27017'; // REPLACE WITH YOUR OWN SERVER
const database = 'test';          // REPLACE WITH YOUR OWN DB NAME

mongoose.connect(`mongodb://${server}/${database}`, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    useFindAndModify: false,
    useCreateIndex: true
}).then(() => {
    console.log('MongoDB connected!!');
}).catch(err => {
    console.log('Failed to connect to MongoDB', err);
});

If you need to connect with additional databases, use the mongoose.createConnection() method.

Defining the Schema

Before you can do anything with Mongoose, you need to define a schema. A Mongoose schema defines the structure of the document, document properties, default values, validators, and more.

All the keys names in a schema correspond to the properties names in a MongoDB collection. For example, here is what a Book schema might potentially look like:

const bookSchema = new mongoose.Schema({
    name: { type: String, required: true },
    author: String,
    isbn: { type: String, unique: true },
    created: { type: Date, default: Date.now }
});

As you can see above, we have defined several properties in bookSchema. We have also specified several constraints like required and unique. If any of these constraints is violated when saving the document, the operation will fail and an error will be thrown.

Here is the list of all valid schema types that Mongoose supports:

  • String
  • Number
  • Date
  • Buffer
  • Boolean
  • Mixed
  • ObjectId
  • Array
  • Decimal128
  • Map
  • Schema

Take a look at this article to learn more about Mongoose schema and how to define indexes, static and virtual methods, query helpers, aliases, and more.

Defining a Model

A Mongoose model is what you actually need in order to interact with MongoDB. It is the compiled version of the schema that maps directly to a single document in the collection.

You can use a Mongoose model for updating, creating, querying, and removing documents from a MongoDB collection.

To create a Book model for the bookSchema defined above, you can use the mongoose.model() method and pass it the name of the collection and a reference to the schema definition:

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

Note that the mongoose.model() method will pluralize and lowercase the name of the collection you specified. For example, the actual MongoDB collection name for the Book model should be books. To retrieve all books using the Mongo Shell, you would use the db.books.find() command.

CRUD Operations

Once you have created the model, you can ready to basic perform CRUD operations, plus other operations like aggregations, count, and more.

Mongoose provides a very flexible API to perform different operations in many ways.

Creating a document

To create a new document, just create a new instance of the model and then save it to the database by calling the save() method on the object.

Here is an example:

const book = new Book({
    name: 'Introduction to Node.js',
    author: 'Atta',
    isbn: 'ABL-4566'
});

book.save()
    .then(book => {
        console.log(book);
    }).catch(err => {
        console.log(err);
    });

The result is a document that is returned upon successful creation:

{
  _id: 5fd329cad20db9eefbd96262,
  name: 'Introduction to Node.js',
  author: 'Atta',
  isbn: 'ABL-4566',
  created: 2020-12-11T08:11:54.236Z,
  __v: 0
}

The _id field is auto-generated by MongoDB and acts as a primary key of the collection. It's unique across all documents in the collection.

The __v field is the version key property set by Mongoose when the document was created. Its value is the internal revision of the document.

Finding a document

Finding a document using Mongoose is very simple. The model class exposes several static and instance methods to perform query operations.

You can query documents by any of the attributes defined in the schema, and even combine multiple properties together.

The following example demonstrates how you can use the find() method to find one or more documents:

Book.find({ isbn: 'ABL-4566' })
    .then(book => {
        console.log(book);
    }).catch(err => {
        console.log(err);
    });

The document returned will be similar to what was displayed when we first created it but wrapped in an array:

[
  {
    _id: 5fd329cad20db9eefbd96262,
    name: 'Introduction to Node.js',
    author: 'Atta',
    isbn: 'ABL-4566',
    created: 2020-12-11T08:11:54.236Z,
    __v: 0
  }
]

The find() method can also be used to find all documents in a collection:

 Book.find()
    .then(books => {
        console.log(books);
    }).catch(err => {
        console.log(err);
    });

If you already know the value of the _id attribute, use findById() method instead:

Book.findById('5fd329cad20db9eefbd96262')
    .then(book => {
        console.log(book);
    }).catch(err => {
        console.log(err);
    });

The findById() method returns a single document that matches the _id attribute key:

{
  _id: 5fd329cad20db9eefbd96262,
  name: 'Introduction to Node.js',
  author: 'Atta',
  isbn: 'ABL-4566',
  created: 2020-12-11T08:11:54.236Z,
  __v: 0
}

Alternatively, you could also use the findOne() method that allows you to use logical $or and $and operators.

For example, we can query Book model by author's name and book's title. If a document is found that matches both fields, it will be returned:

Book.findOne({ $and: [{ author: 'Muller' }, { name: 'Web Security' }] })
    .then(book => {
        console.log(book);
    }).catch(err => {
        console.log(err);
    });

Updating an existing document

To an update an existing document, you use the findOneAndUpdate() method.

As the name suggests, this method finds the matching document in the collection, and update it — all in one transaction:

Book.findOneAndUpdate(
    { author: 'Atta' },
    { author: 'John' },
    { new: true })
    .then(book => {
        console.log(book);
    }).catch(err => {
        console.log(err);
    });

By default, Mongoose does not return the updated document due to performance reasons. But we can ask for it by passing an additional { new: true } parameter.

The document returned by the above query will have the author field updated:

{
  _id: 5fd329cad20db9eefbd96262,
  name: 'Introduction to Node.js',
  author: 'John',
  isbn: 'ABL-4566',
  created: 2020-12-11T08:11:54.236Z,
  __v: 0
}

You could also first retrieve the document, update the fields, and then call the save() method to save changes:

Book.findOne({ isbn: 'ABL-4566' })
    .then(book => {
        // Update Fields
        book.name = 'Node.js Basics';

        // Save Changes
        book.save()
            .then(doc => console.log(doc))
            .catch(err => console.log(err));
    }).catch(err => {
        console.log(err);
    })

Deleting a document

You can use the findOneAndRemove() method to delete a document from a collection. It returns the original document that was removed:

Book.findOneAndDelete({ author: 'John' })
    .then(book => {
        console.log(book);
    }).catch(err => {
        console.log(err);
    });

The deleted document returned will look like the following:

{
  _id: 5fd329cad20db9eefbd96262,
  name: 'Node.js Basics',
  author: 'John',
  isbn: 'ABL-4566',
  created: 2020-12-11T08:11:54.236Z,
  __v: 0
}

Query Building

Mongoose provides an excellent API that allows to incrementally build query components. You can use the query builder to run many complex operations supported by MongoDB.

Let us look at the following query:

Book.find()                         // Find all books
    .skip(10)                       // Skip the first 10 books
    .limit(5)                       // Limit to 5 books only
    .sort({ name: 1 })              // Sort ascending by name
    .select({ name: true })         // Select book name only
    .exec()                         // Execute the query
    .then(books => {
        console.log(books);
    })
    .catch(err => {
        console.error(err);
    });

The above query does the following in a single operation:

  1. Find all books in the collection
  2. Skip the first 10 documents
  3. Limit the result to the top 5 documents
  4. Sort the result by book's name
  5. Select the name field
  6. Execute the query and return the result

Conclusion

Mongoose is a popular object data modeling tool for Node.js and MongoDB. It provides a very flexible yet powerful API to create, update, query, and remove documents from MongoDB.

While you can use the MongoDB native driver to interact directly with the database, Mongoose simplifies the process by providing a higher abstraction layer. Under the hood, Mongoose also uses the MongoDB native driver.

Take a look at this article to learn how to access and query MongoDB by using the Mongo shell.

✌️ Like this article? Follow me on Twitter and LinkedIn. You can also subscribe to RSS Feed.