Tutorial: Get Started with Mongoose
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], 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
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.
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 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.
where()
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.
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", ...
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], createdAt: Date, updated: Date, comments: [{ user: String, content: String, votes: Number }] }); const Blog = model('Blog', blogSchema); export default Blog;
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.
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.
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 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.
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 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}
.
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 constructors 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:
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.
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 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.
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 to incorporate Mongoose helper methods and build more complex queries.
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.