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],
createdAt: Date,
updated: Date,
comments: [{
user: String,
content: String,
votes: Number
}]
});

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

  • Decimal128

  • 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.

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 ObjectId of a 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 queries. The following is an example of performing a find operation by using findOne() and the equivalent approach by using the where() method:

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

You can use either method to perform find operations. You can also chain multiple where() methods to build increasingly complex queries.

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.where("author").equals("Jess Garcia").select("title author");
console.log(blog);

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

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:

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",
...
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],
createdAt: Date,
updated: Date,
comments: [{
user: String,
content: String,
votes: Number
}]
});
const Blog = model('Blog', blogSchema);
export default Blog;
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 run the code, use the following command in your terminal:

npm run dev
Created article: {
title: 'Awesome Post!',
slug: 'awesome-post',
published: true,
content: 'This is the best post ever',
tags: [ 'featured', 'announcement' ],
_id: new ObjectId('...'),
comments: [],
__v: 0
}

Note

When you use nodemon, the code runs every time you save a file. Saving will insert multiple articles into your database.

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 get a document by its ObjectId.

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

// 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

The Mongoose exec() function is an option of the findById() method that returns a promise. To learn more about working with promises and 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 no 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 only run on the create() and save() methods.

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 preceding code, the slug field is defined as a string that is always in lowercase. This validation takes the slug input and converts it to lowercase before saving the document to the database.

For the createdAt date field, you can set the default by using an arrow function. This field is also specified to be impossible to change later by setting immutable to true.

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,
lowercase: true,
},
published: {
type: Boolean,
default: false,
},
author: {
type: String,
required: true,
},
content: String,
tags: [String],
createdAt: {
type: Date,
default: () => Date.now(),
immutable: true,
},
updated: Date,
comments: [{
user: String,
content: String,
votes: Number
}]
});

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}.

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 constructors 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:

import mongoose from 'mongoose';
const { Schema, SchemaTypes, model } = mongoose;
const blogSchema = new Schema({
...
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:

const blogSchema = new Schema({
...,
comments: [{
user: {
type: SchemaTypes.ObjectId,
ref: 'User',
required: true,
},
content: String,
votes: Number
}];
});
...

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 use the Mongoose populate() method. 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
}

This populates the data for the author field with the data for this article. Mongoose uses the MongoDB $lookup method to populate the data automatically.

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 automatically updates the updated field of a Blog instance before saving or updating.

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

blogSchema.pre('save', function(next) {
this.updated = Date.now();
next();
});

To see the effect of this function, add the following code to your index.js file to find an article, update the title, and then save the updated article:

// Uses middleware to update the updated field before saving and updating
// Create a new article
const articleMiddlewareUpdate = await Blog.create({
title: 'Another Awesome Post!',
slug: 'Another-Awesome-Post',
author: user._id,
content: 'Here is another awesome post',
});
console.log('Original Article: ', articleMiddlewareUpdate);
// Wait
await new Promise(resolve => setTimeout(resolve, 1000));
// Update the article
articleMiddlewareUpdate.content = "Check my updated field"
await articleMiddlewareUpdate.save();
console.log('Auto-updated Article:', articleMiddlewareUpdate);
Original Article: {
title: 'Another Awesome Post!',
slug: 'another-awesome-post',
published: false,
author: new ObjectId('683df165ce6200f8fafc9c95'),
content: 'Here is another awesome post',
tags: [],
_id: new ObjectId('683df167ce6200f8fafc9ca1'),
createdAt: 2025-06-02T18:45:59.892Z,
comments: [],
updated: 2025-06-02T18:45:59.894Z,
__v: 0
}
Auto-updated Article: {
title: 'Another Awesome Post!',
slug: 'another-awesome-post',
published: false,
author: new ObjectId('683df165ce6200f8fafc9c95'),
content: 'Check my updated field',
tags: [],
_id: new ObjectId('683df167ce6200f8fafc9ca1'),
createdAt: 2025-06-02T18:45:59.892Z,
comments: [],
updated: 2025-06-02T18:46:01.034Z,
__v: 0
}

When you run the application, you can see that the original article has an updated value, though one was not specified. You can also see that the updated value increase when the article is modified.

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 to incorporate Mongoose helper methods and build more complex queries.

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