Mongoose Tutorial for Node.js — Schemas, Models & CRUD
Written By: Avinash Malhotra
Updated on
Mongoose is a popular Object Document Mapper (ODM) for MongoDB in Node.js. This tutorial shows how to define schemas, create models, and perform CRUD operations with Mongoose and Express, with examples for installation, configuration, queries, sorting, and pagination.
What is Mongoose ODM?
Mongoose is MongoDB's Object Document Mapper ( ODM ) designed to work in an asynchronous environment. MongoDB stores data in JSON-like documents, and Mongoose provides a schema-based abstraction layer that simplifies working with MongoDB in Node.js applications. Using Mongoose, developers can map JSON documents to full JavaScript Objects with data validation, schemas, middleware, and business logic.
Mongoose provides a comprehensive set of features including schema-based solutions, type casting, validation rules, query building, business logic hooks, and many other out-of-the-box features that make working with MongoDB more productive.
In traditional SQL databases, developers use ORM (Object Relational Mapping), but in NoSQL databases like MongoDB, developers use ODM (Object Document Mapper) for the same purpose.
Key Features of Mongoose
- Schema Validation: Define data structure and enforce validation rules
- Type Casting: Automatic type conversion for document fields
- Middleware/Hooks: Pre and post hooks for document operations
- Query Building: Chainable query API for flexible data retrieval
- Virtuals: Computed properties not stored in MongoDB
- Population: Automatic reference resolution between documents
- Indexing: Support for MongoDB indexes to optimize queries
- Promise Support: Native promises and async/await compatibility
Install Mongoose
Mongoose is available on npm (Node Package Manager) as the mongoose package. You can easily install it using npm and include the mongoose module in your main application file.
This command installs Mongoose and its dependencies in your Node.js project. Mongoose is the recommended way to interact with MongoDB in Node.js applications.
Configure Mongoose Connection
Configuring Mongoose requires establishing a connection to your MongoDB instance. Below are examples for both modern (Mongoose 6+) and legacy (Mongoose 5 and below) versions.
Promise-based API for Mongoose 6 and above
Modern Mongoose versions use async/await and promises for better code readability and error handling.
/*dao.js*/
// Connection setup with Mongoose 6+
const mongoose = require('mongoose');
main().catch(err => console.log(err));
async function main() {
await mongoose.connect('mongodb://127.0.0.1:27017/node');
// use `await mongoose.connect('mongodb://user:password@127.0.0.1:27017/test');` if authentication is enabled
console.log("Database Connected Successfully");
}
Callback-based API for Mongoose 5 and below
Legacy Mongoose versions use callback-based connection handling. While still functional, promise-based approach is recommended for new projects.
/*dao.js*/
const mongoose=require('mongoose');
mongoose.connect('mongodb://127.0.0.1:27017/node').then(() => console.log('Connected to MongoDB!'));
const db=mongoose.connection;
db.on('error', function (err) { throw err });
db.once('open', function callback() {
console.log('connected!');
db.close();
});
Run Mongoose Connection
Execute your connection file to test the MongoDB connection and verify that Mongoose is properly configured.
Database Connected Successfully
Creating Mongoose Schemas
Mongoose Schema is a blueprint that defines the structure, field types, and validation rules for MongoDB documents. Each schema contains key-value pairs that specify field names and their configurations. Schemas enforce data consistency and provide type safety.
Supported Schema Field Types
Mongoose supports various data types for schema fields, allowing you to model different kinds of data:
- String: Text data
- Number: Integer and decimal values
- Date: Date and time values
- Buffer: Binary data
- Boolean: True/false values
- ObjectId: References to other MongoDB documents
- Array: Collections of values or nested documents
- Decimal128: High-precision decimal numbers
- Map: Key-value pairs with dynamic keys
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Define a basic schema with field types
const CarSchema = new Schema({
_id: mongoose.ObjectId,
name: String,
type: String,
price: Number
}, {collection: "cars"});
Schema with Validation Rules
Mongoose allows you to add validation rules to schema fields. Validation ensures data integrity by enforcing constraints on the data being saved to MongoDB.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Schema with comprehensive validation rules
const CarSchema = new Schema({
_id: mongoose.ObjectId,
name: {
type: String,
required: true, // Field is mandatory
unique: true, // Each value must be unique
trim: true, // Remove whitespace
minlength: 2, // Minimum string length
maxlength: 100 // Maximum string length
},
type: {
type: String,
required: true,
enum: ['sedan', 'suv', 'hatchback', 'coupe'] // Only allow specific values
},
price: {
type: Number,
required: true,
min: 0, // Minimum value
max: 10000000 // Maximum value
},
year: {
type: Number,
min: 1900,
max: new Date().getFullYear()
}
}, {collection: "cars"});
Creating Mongoose Models
The next step after defining a schema is to create a model from that schema using mongoose.model(). A model is a constructor that compiles your schema definition and represents a MongoDB collection. Each model instance represents a document in that collection.
The optional second configuration object uses the collection property to specify the MongoDB collection name. If you don't specify a collection name, Mongoose automatically pluralizes the model name.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Define the schema
const CarSchema = new Schema({
_id: mongoose.ObjectId,
name: { type: String, required: true, unique: true },
type: { type: String, required: true },
price: { type: Number, required: true },
date: { type: Date, default: Date.now },
}, {collection: "cars"});
// Compile schema to model
const Car = mongoose.model("Car", CarSchema);
Creating Model Instances for Database Operations
After defining a model, you can create instances of that model to perform database operations. Each instance represents a document that can be saved to MongoDB.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Define schema
const CarSchema = new Schema({
_id: mongoose.ObjectId,
name: { type: String, required: true },
type: { type: String, required: true },
price: { type: Number, required: true },
date: { type: Date, default: Date.now },
}, {collection: "cars"});
// Create model
const Car = mongoose.model("Car", CarSchema);
// Create an instance of the model
const newCar = new Car({
_id: new mongoose.Types.ObjectId(),
name: 'Honda Civic',
type: 'sedan',
price: 1500000
});
Insert Data into MongoDB Collections
To persist data to MongoDB, use the model.save() method on a model instance. This method writes the document to the database collection. The example below demonstrates inserting car data into a 'cars' collection.
/*dao.js - Complete example of inserting documents*/
const mongoose = require('mongoose');
// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/cars', {
useNewUrlParser: true,
useUnifiedTopology: true
});
const db = mongoose.connection;
const Schema = mongoose.Schema;
// Define the car schema with validation
const CarSchema = new Schema({
_id: mongoose.ObjectId,
name: { type: String, required: true },
type: { type: String, required: true },
price: { type: Number, required: true },
date: { type: Date, default: Date.now }
}, {collection: 'suzuki'});
// Create model from schema
const Car = mongoose.model("Car", CarSchema);
// Create a new car document
const newCar = new Car({
_id: new mongoose.Types.ObjectId(),
name: "Swift",
type: "hatchback",
price: 800000
});
// Handle database connection events
db.on('error', function(err){ throw err });
db.once('open', function() {
console.log('Database connected!');
// Save the document to database
newCar.save()
.then(savedCar => {
console.log("Data saved successfully:", savedCar);
res.status(200).send("Data saved successfully");
})
.catch(error => {
console.error("Error saving data:", error);
res.status(400).send("Error saving data");
});
});
Execute Insert Operation
Swift saved to Suzuki collection successfully.
Read Data from MongoDB Collections
To retrieve documents from MongoDB, use the model.find() method. You can search for documents matching specific criteria and retrieve all matching results. This example demonstrates querying the 'cars' collection.
/*dao.js - Query example*/
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/cars', {
useNewUrlParser: true,
useUnifiedTopology: true
});
const db = mongoose.connection;
db.on('error', function (err) { throw err });
db.once('open', function() {
console.log('Mongoose connected!');
const Schema = mongoose.Schema;
const CarSchema = new Schema({
_id: mongoose.ObjectId,
name: String,
type: String,
price: Number,
}, {collection: 'cardata'});
const Car = mongoose.model("Car", CarSchema);
// Find all cars with name 'Swift'
Car.find({name: "swift"})
.then(results => {
console.log("Found cars:", results);
})
.catch(err => {
console.error("Query error:", err);
});
// Legacy callback-based approach (not recommended)
/*
Car.find({name:"swift"}, (err, data) => {
if(err){
console.error(err);
} else {
console.log(data);
}
});
*/
});
Query Output
Mongoose connected!
[
{
_id: 5eb81a2fbe672314a543269e,
name: 'swift',
type: 'hatchback',
price: 800000
}
]
Query Specific Fields Only
You can use MongoDB's field projection to retrieve only specific fields from matching documents, reducing data transfer and improving performance. Use the select() method or projection object.
// Method 1: Using select()
Car.find({type: "hatchback"})
.select('name type -_id') // Include name and type, exclude _id
.then(results => console.log(results))
.catch(err => console.error(err));
// Method 2: Using projection object
Car.find({type: "hatchback"}, {name: 1, type: 1, _id: 0})
.then(results => console.log(results))
.catch(err => console.error(err));
Projection Output
Mongoose connected!
[
{
name: 'swift',
type: 'hatchback'
}
]
Limit Query Results for Pagination
Use the limit() method to restrict the number of documents returned by a query. This is essential for pagination and improving query performance when working with large datasets.
// Limit results to 2 documents
Car.find({type: "hatchback"})
.limit(2)
.then(results => console.log(results))
.catch(err => console.error(err));
Limit Query Output
Mongoose connected!
[
{
_id: 5eb81a2fbe672314a543269e,
name: 'swift',
type: 'hatchback'
},
{
_id: 5eb5378791fc73f290f38252,
name: 'baleno',
type: 'hatchback'
}
]
Sort Query Results in Ascending or Descending Order
The sort() method orders query results by a specific field. Use 1 for ascending order (A-Z, 0-9) and -1 for descending order (Z-A, 9-0).
// Sort cars by name in ascending order
Car.find({})
.sort({name: 1}) // 1 = ascending, -1 = descending
.then(results => console.log(results))
.catch(err => console.error(err));
// Sort by price in descending order
Car.find({})
.sort({price: -1})
.then(results => console.log(results));
Sort Query Output
Mongoose connected!
[
{
_id: 5eb5378791fc73f290f38252,
name: 'baleno',
type: 'hatchback'
},
{
_id: 5eb81a2fbe672314a543269e,
name: 'swift',
type: 'hatchback'
}
]
Frequently Asked Questions
What is Mongoose?
Mongoose is an ODM (Object Document Mapper) that provides a schema-based solution to model application data for MongoDB in Node.js.
How do I install Mongoose?
Install via npm with npm i mongoose and require it in your Node.js app using const mongoose = require('mongoose');.