Mongoose ODM
What is Mongoose?
Mongoose is an Object Data Modeling (ODM) library for MongoDB and Node.js. It provides a schema-based solution to model your application data, with built-in type casting, validation, query building, and business logic hooks. Think of it as an ORM for MongoDB.
Installation and Connection
// Install Mongoose
npm install mongoose
// Connect to MongoDB
const mongoose = require("mongoose")
mongoose.connect("mongodb://localhost:27017/myapp", {
useNewUrlParser: true,
useUnifiedTopology: true
})
const db = mongoose.connection
db.on("error", console.error.bind(console, "Connection error:"))
db.once("open", () => {
console.log("Connected to MongoDB via Mongoose!")
})
// Or using async/await
async function connectDB() {
try {
await mongoose.connect("mongodb+srv://user:pass@cluster0.abc123.mongodb.net/myapp")
console.log("MongoDB connected")
} catch (err) {
console.error("Connection failed:", err)
process.exit(1)
}
}
Defining Schemas and Models
const { Schema, model } = require("mongoose")
const userSchema = new Schema({
name: {
type: String,
required: [true, "Name is required"],
minlength: 2,
maxlength: 100,
trim: true
},
email: {
type: String,
required: true,
unique: true,
lowercase: true,
match: [/^\S+@\S+\.\S+$/, "Invalid email format"]
},
age: {
type: Number,
min: [0, "Age cannot be negative"],
max: [150, "Age seems too high"]
},
role: {
type: String,
enum: ["admin", "editor", "user"],
default: "user"
},
active: {
type: Boolean,
default: true
},
hobbies: [String],
address: {
street: String,
city: String,
zip: String
},
createdAt: {
type: Date,
default: Date.now
}
}, {
timestamps: true // auto-adds createdAt and updatedAt
})
// Create the model (maps to "users" collection)
const User = model("User", userSchema)
module.exports = User
CRUD with Mongoose
// CREATE
const alice = new User({ name: "Alice", email: "alice@example.com", age: 29 })
await alice.save()
// Or use create() shorthand
const bob = await User.create({ name: "Bob", email: "bob@example.com", age: 34 })
// READ
const allUsers = await User.find()
const activeUsers = await User.find({ active: true }).sort({ name: 1 }).limit(10)
const alice = await User.findOne({ email: "alice@example.com" })
const userById = await User.findById("64a1f2c3e4b0a1b2c3d4e5f6")
// Projection — select specific fields
const names = await User.find({}, "name email -_id")
// UPDATE
await User.findByIdAndUpdate(
"64a1f2c3e4b0a1b2c3d4e5f6",
{ $set: { age: 30 } },
{ new: true, runValidators: true } // new: true returns updated doc
)
await User.updateMany({ role: "user" }, { $set: { active: false } })
// DELETE
await User.findByIdAndDelete("64a1f2c3e4b0a1b2c3d4e5f6")
await User.deleteMany({ active: false })
Virtuals, Middleware, and Populate
// VIRTUAL — computed property not stored in DB
userSchema.virtual("fullName").get(function() {
return `${this.firstName} ${this.lastName}`
})
// PRE HOOK — runs before save
userSchema.pre("save", async function(next) {
if (this.isModified("password")) {
this.password = await bcrypt.hash(this.password, 12)
}
next()
})
// POST HOOK — runs after save
userSchema.post("save", function(doc) {
console.log("User saved:", doc._id)
})
// POPULATE — resolve references to other collections
const orderSchema = new Schema({
userId: { type: Schema.Types.ObjectId, ref: "User" },
total: Number
})
const Order = model("Order", orderSchema)
// Populate userId with the full User document
const orders = await Order.find().populate("userId", "name email")
// orders[0].userId is now the full User object, not just an ObjectId
// Mongoose Schema Types:
// String, Number, Date, Buffer, Boolean, Mixed, ObjectId, Array, Decimal128, Map
// Common field options:
// type — data type
// required — true or [true, "error message"]
// default — default value or function
// unique — creates a unique index
// index — creates an index
// sparse — sparse index
// min/max — for Number and Date
// minlength/maxlength — for String
// enum — array of allowed values
// match — regex pattern for String
// trim — remove whitespace
// lowercase — convert to lowercase
// uppercase — convert to uppercase
// immutable — cannot be changed after creation
// validate — custom validator function
// Example with custom validator
score: {
type: Number,
validate: {
validator: (v) => v >= 0 && v <= 100,
message: "Score must be between 0 and 100"
}
}