Overview
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.
Schemas
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
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.
Tutorial
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
Verify the prerequisites
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.
Set up your environment
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.
Connect to MongoDB
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.
Create a schema and a model
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.
Perform CRUD operations
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.
Insert Data
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.
Update Data
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 }
Find Data
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.
Specify Document Fields
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' }
Delete Data
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 }
Validate your data
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.
Introduce multiple schemas
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
fieldtype
toSchemaTypes.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.
Add middleware
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.
Next Steps
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.
Mongoose Custom Setters
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.
Helper Methods
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.
exists()
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.
where()
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:
Additional Resources
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.