Docs Menu
Docs Home
/ / /
Node.js Driver
/

Tutorial: Get Started with Mongoose

Mongoose is an Object Data Modeling (ODM) library for MongoDB. You can use Mongoose to help with data modeling, schema enforcement, model validation, and general data manipulation. Because MongoDB has a flexible data model that allows you to alter and update databases in the future, there aren't rigid schemas. However, Mongoose enforces a semi-rigid schema from the beginning, so you must define a schema and model.

A schema defines the structure of your collection documents. A Mongoose schema maps directly to a MongoDB collection.

The following example creates a new Schema named blog:

const blog = new Schema({
title: String,
slug: String,
published: Boolean,
author: String,
content: String,
tags: [String],
comments: [{
user: String,
content: String,
votes: Number
}]
}, {
timestamps: true
});

When you create a schema, you must define each field and its data types. The following types are permitted:

  • String

  • Number

  • Date

  • Buffer

  • Boolean

  • Mixed

  • ObjectId

  • Array

  • Int32

  • Decimal128

  • Double

  • Map

Models take your schema and apply it to each document in its collection. Models are responsible for all document interactions such as create, read, update, and delete (CRUD) operations.

Tip

The first argument you pass to the model is the singular form of your collection name. Mongoose automatically changes this to the plural form, transforms it to lowercase, and uses that for the database collection name.

The following example shows how to declare a model named Blog that uses the blog schema:

const Blog = mongoose.model('Blog', blog);

In the preceding example, Blog translates to the blogs collection in MongoDB.

In this tutorial, you will perform the following actions:

  • Set up your environment to use Mongoose

  • Connect to MongoDB

  • Create a Mongoose schema and model

  • Insert, update, find, and delete data

  • Project document fields

  • Validate your data

  • Use multiple schemas and middleware

1

Before you begin this tutorial, ensure you have the following components prepared:

  • An MongoDB Atlas account with a configured cluster. To view instructions, see the Get Started with Atlas guide.

  • Node.js v16.20.1 or later.

2

This tutorial uses nodemon to run the code locally. Run the following commands in your terminal to initialize your project and install the necessary dependencies:

mkdir mongodb-mongoose
cd mongodb-mongoose
npm init -y
npm i mongoose
npm i -D nodemon

Open your project in your preferred code editor. This tutorial uses ES Modules instead of CommonJS. You must add the module type to use ES Modules. Go to the package.json file and add the following code:

...
"scripts": {
"dev": "nodemon index.js"
},
"type": "module",
...

Start your application with nodemon by running the following command:

npm run dev

Note

When you use nodemon, the code runs every time you save a file.

3

In the root level of your project, create a file named index.js and add the following code to the top of the file:

import mongoose from 'mongoose';
mongoose.connect("<connection string>")

Replace the <connection string> placeholder with your MongoDB Atlas connection string. For more information on how to find your connection string, see the Connect to Your Cluster tutorial in the Atlas documentation.

4

Before you start adding and updating data in MongoDB, you must create a schema and model.

With Mongoose, you create a schema model file for each schema that is needed. First, create a folder called model to put all your schema files in, then create your first schema file called Blog.js. Open this file and add the following code:

import mongoose from 'mongoose';
const { Schema, model } = mongoose;
const blogSchema = new Schema({
title: String,
slug: String,
published: Boolean,
author: String,
content: String,
tags: [String],
comments: [{
user: String,
content: String,
votes: Number
}]
}, {
timestamps: true
});
const Blog = model('Blog', blogSchema);
export default Blog;

This schema enables a timestamps option, which adds mongoose-manged createdAt and updatedAt fields to the schema that are updated automatically. For more information, see the Timestamps page in the Mongoose documentation.

5

You now have your first model and schema set up, and you can start inserting data into the database. The following sections show you how to perform CRUD operations using Mongoose.

Go to index.js and add the following import statement to the top of your file:

import Blog from './model/Blog.js';

Add the following code below the line that contains your connection string:

// Creates a new blog post and inserts it into database
const article = await Blog.create({
title: 'Awesome Post!',
slug: 'awesome-post',
published: true,
content: 'This is the best post ever',
tags: ['featured', 'announcement'],
});
console.log('Created article:', article);

The preceding code uses the Mongoose create() method to instantiate the object representing a blog article, and then saves it to the database. The returned document logs to the console, including its _id. Take note of this _id for use in future steps.

To update data, you can directly edit the local object with Mongoose. Then, you can use the save() method to write the update to the database.

Add the following code to update the article you inserted in the previous section:

// Updates the title of the article
article.title = "The Most Awesomest Post!!";
await article.save();
console.log('Updated Article:', article);
Updated Article: {
title: 'The Most Awesomest Post!!',
slug: 'awesome-post',
published: true,
content: 'This is the best post ever',
tags: [ 'featured', 'announcement' ],
_id: new ObjectId('...'),
comments: [],
__v: 0
}

To find a specific document, you can use the Mongoose findById() method to query for a document by specifying its _id.

Add following code to your index.js file to find a specific article by its _id:

// Finds the article by its ID. Replace <object id> with the objectId of the article.
const articleFound = await Blog.findById("<object id>").exec();
console.log('Found Article by ID:', articleFound);
Found Article by ID: {
_id: new ObjectId('68261c9dae39e390341c367c'),
title: 'The Most Awesomest Post!!',
slug: 'awesome-post',
published: true,
content: 'This is the best post ever',
tags: [ 'featured', 'announcement' ],
comments: [],
__v: 0
}

Note

By default, Mongoose queries return thenables, which are objects with some properties of a JavaScript Promise. You can append the exec() method to a query to receive a true JavaScript Promise. To learn more about working with promises in Mongoose, see the Promises guide in the Mongoose documentation.

You can use Mongoose to project only the fields that you need. The following code specifies to only project the title, slug, and content fields.

Add the following code to your index.js file, replacing the <object id> placeholder with the ObjectId value for the document that you inserted previously:

// Finds the article by its ID and projects only the title, slug, and content fields.
// Replace <object id> with the objectId of the article.
const articleProject = await Blog.findById("<object id>", "title slug content").exec();
console.log('Projected Article:', articleProject);
Projected Article: {
_id: new ObjectId('...'),
title: 'The Most Awesomest Post!!',
slug: 'awesome-post',
content: 'This is the best post ever'
}

Mongoose uses the deleteOne() and deleteMany() methods to delete data from a collection. You can specify the field of the document you want to delete and pass that field to the method that you choose.

Add the following code to your index.js file to delete one article from the database:

// Deletes one article with the slug "awesome-post".
const blogOne = await Blog.deleteOne({ slug: "awesome-post" });
console.log('Deleted One Blog:', blogOne);
Deleted One Blog: { acknowledged: true, deletedCount: 1 }

To delete multiple articles, you can add the following code:

// Deletes all articles with the slug "awesome-post".
const blogMany = await Blog.deleteMany({ slug: "awesome-post" });
console.log('Deleted Many Blogs:', blogMany);
Deleted Many Blogs: { acknowledged: true, deletedCount: 4 }
6

The articles inserted in the previous steps do not contain the author, dates, or comments fields, even though these fields are included in the schema. This is because although you defined the structure of your document, you have not defined which fields are required. Any field that is not defined as required can be omitted.

In Mongoose, when you include validation on a field, you must pass an object as its value.

Note

Validators automatically run on the create() and save() methods. You can specify them to run them on update methods, such as update() and updateOne() by setting the runValidators option to true. For more information about validators, see the Validation page in the Mongoose documentation.

You can use several validation methods with Mongoose. For example, you can set required to true on any fields that you want to require. You can also validate the type and the formatting. In the following code, the slug field is defined as a string with a minLength of 4. This means that providing a slug with fewer than 4 characters will result in a ValidationError.

To add data validation and define these requirements, update the schema in Blog.js as shown in the following example:

const blogSchema = new Schema({
title: {
type: String,
required: true,
},
slug: {
type: String,
required: true,
minLength: 4,
},
published: {
type: Boolean,
default: false,
},
author: {
type: String,
required: true,
},
content: String,
tags: [String],
comments: [{
user: String,
content: String,
votes: Number
}]
}, {
timestamps: true
});

After adding this validation, your application will crash. Add an author field to the create() call in your index.js file to meet the new validation requirement:

// Creates a new blog post and inserts it into database
const article = await Blog.create({
title: 'Awesome Post!',
slug: 'awesome-post',
published: true,
author: 'A.B. Cee',
content: 'This is the best post ever',
tags: ['featured', 'announcement'],
});

Tip

When you use schemas with Mongoose, value: String is the same as value: {type: String}.

For more information, see the Validation page in the Mongoose documentation.

7

Next, you can add more complexity to your author field by creating a another schema, and nesting it in the blog schema.

In the model folder, create a new file named User.js. Add the following code to this file:

import mongoose from 'mongoose';
const {Schema, model} = mongoose;
const userSchema = new Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
minLength: 10,
required: true,
lowercase: true
},
});
const User = model('User', userSchema);
export default User;

To use your new User model to define the author field in the blog schema, update the Blog.js file with the following changes:

  • Add SchemaTypes to the list of properties extracted from the Mongoose library.

  • Change the author field type to SchemaTypes.ObjectId and add a reference (ref) to the 'User' model.

The following code shows these updates:

const { Schema, SchemaTypes, model } = mongoose;
... // Inside Schema block:
author: {
type: SchemaTypes.ObjectId,
ref: 'User',
required: true,
},
...

You can reuse the same User model for the comment.user field by changing the blogSchema definition:

... // Inside Schema block:
comments: [{
user: {
type: SchemaTypes.ObjectId,
ref: 'User',
required: true,
},
...

To use the new user model in your application, go to the index.js file. Add the following code to the top of the file to import the user model:

import User from './model/User.js';

Because you added field validation to the blog schema, the previous code to insert, update, and delete blogs, and to specify fields to project, won't pass the validation and the application will error.

Create a new user by adding the following create() call:

// Create a new user
const user = await User.create({
name: 'Jess Garica',
email: 'jgarcia@email.com',
});

Update the existing create() call with the following code to create a new article that uses the new user as the author, as shown in the following code:

// Creates a new blog post that references the user as the author
const articleAuthor = await Blog.create({
title: 'Awesome Post!',
slug: 'Awesome-Post',
author: user._id,
content: 'This is the best post ever',
tags: ['featured', 'announcement'],
});
console.log('Article with Author:', articleAuthor);
Article with Author: {
title: 'Awesome Post!',
slug: 'awesome-post',
published: false,
author: new ObjectId('...'),
content: 'This is the best post ever',
tags: [ 'featured', 'announcement' ],
_id: new ObjectId('...'),
createdAt: 2025-05-15T18:05:23.780Z,
comments: [],
__v: 0
}

The preceding code adds a users collection with the blogs collection in the MongoDB database. This code adds the required author field and sets its value to the user._id.

To add the values of the name and email fields to the author field for the article, you can append the Mongoose populate() method to your Blog query. The populate() method will perform a second query for the User document referenced by user._id.

Add the following code to index.js to populate this data:

// Populates the author field with user data
const articlePopulate = await Blog.findOne({ title: "Awesome Post!" }).populate("author");
console.log('Article Populated:', articlePopulate);
Article Populated: {
_id: new ObjectId('...'),
title: 'Awesome Post!',
slug: 'awesome-post',
published: false,
author: {
_id: new ObjectId('...'),
name: 'Jess Garica',
email: 'jgarcia@email.com',
__v: 0
},
content: 'This is the best post ever',
tags: [ 'featured', 'announcement' ],
createdAt: 2025-05-15T18:04:28.590Z,
comments: [],
__v: 0
}

For more information, see the Document.populate() page of the Mongoose API documentation.

8

In Mongoose, middleware are functions that run before, or during, the execution of asynchronous functions at the schema level.

One example of middleware is a function that validates the email field of a User instance before saving or updating.

This example uses the validator package. You can install the validator package by running the following command:

npm install validator

To add this middleware function, add the following code to the userSchema declaration in your User.js file:

import validator from 'validator';
userSchema.pre('save', function (next) {
const user = this;
// Normalize email
if (user.email) {
user.email = user.email.trim().toLowerCase();
}
// Validate email format
if (!validator.isEmail(user.email)) {
return next(new Error('Invalid email format'));
}
next();
});

To see the effect of this function, modify the user.email field in your index.js file to make it an invalid email address:

const user = await User.create({
name: 'Jess Garica',
email: 'jgarciaemail.com',
});
Error: Invalid email format

If you correct the email address, you can see that the error is not thrown.

Besides the pre() middleware function, Mongoose also offers a post() middleware function. For more information about middleware, see the Middleware page in the Mongoose documentation.

You now have a sample project that uses Mongoose to perform CRUD operations on a MongoDB collection. From here, you can choose to build on this project with more complex queries or document validation. The following sections includes some examples of Mongoose features that you might use to in your application.

Custom setters modify your data when it is saved, and can be implemented similar to validators. Mongoose provides the following custom setters:

  • lowercase

  • uppercase

  • trim

The following example lowercases the characters in the blog.slug field:

const blogSchema = new Schema({
...
slug: {
type: String,
required: true,
minLength: 4,
lowercase: true,
},
...
});

For more information, see the SchemaStringOptions page in the Mongoose API documentation.

Mongoose includes several helper methods that are abstracted from regular MongoDB methods. In this section, you can find examples of some of these methods. These methods are not used specifically in this tutorial, but they are helpful to reference when getting started with Mongoose.

The exists() method returns either null or the first document that matches the provided query. The following is an example of matching an article based on the author field:

const blog = await Blog.exists({ author: 'Jess Garcia' });
console.log(blog);

For more information, see the Model.exists() section of the Mongoose API documentation.

The where() method allows you to chain and build complex queries.

The following is an example of performing a find operation by using findOne() with a plain query object and the equivalent query with the where() method:

const blogFind = await Blog.findOne({ author: "Jess Garcia" });
console.log(blogFind);
const blogWhere = await Blog.findOne().where("author").equals("Jess Garcia");
console.log(blogWhere);

In this implementation, the where() implementation starts with a findOne() which tells Mongoose to treat it as a findOne() query. This is important because if you use where() on its own (Blog.where(...)), Mongoose implicitly treats it as a find() operation.

Generally, the where() method is used for complex queries involving dynamic query building or multiple comparators, or when using this method improves readability. There is no performance difference between using the where() method or a plain object query.

To include projection when using the where() method, chain the select() method after your query, as shown in the following example:

const blog = await Blog.findOne().where("author").equals("Jess Garcia").select("title author");
console.log(blog);

For more information, see the following sections of the Mongoose API documentation:

To learn more about using Mongoose with MongoDB, see the Mongoose documentation.

To find support or to contribute to the MongoDB community, see the MongoDB Developer Community page.

Back

Third-Party Integrations

On this page