Install Mongoose in your project:
npm install mongoose
Install Mongoose in your project:
const mongoose = require("mongoose");
// Connect to local MongoDB
mongoose.connect("mongodb://localhost/mydatabase", {
useNewUrlParser: true,
useUnifiedTopology: true,
});
// Connect to MongoDB Atlas
mongoose.connect(
"mongodb+srv://<username>:<password>@cluster0.mongodb.net/mydatabase",
{
useNewUrlParser: true,
useUnifiedTopology: true,
}
);
// Handle connection events
const db = mongoose.connection;
db.on("error", console.error.bind(console, "connection error:"));
db.once("open", function () {
console.log("Connected to MongoDB");
});
Schemas define the structure of documents in a collection.
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
// Basic schema
const userSchema = new Schema({
name: String,
email: String,
age: Number,
});
// Schema with more options
const productSchema = new Schema({
name: {
type: String,
required: true,
trim: true,
},
price: {
type: Number,
min: 0,
},
category: {
type: String,
enum: ["Electronics", "Books", "Clothing"],
},
inStock: {
type: Boolean,
default: true,
},
createdAt: {
type: Date,
default: Date.now,
},
});
Models are fancy constructors compiled from Schema definitions.
const User = mongoose.model("User", userSchema);
const Product = mongoose.model("Product", productSchema);
// Create a single document
const newUser = new User({
name: "John Doe",
email: "john@example.com",
age: 30,
});
newUser.save((err, user) => {
if (err) return console.error(err);
console.log("User saved:", user);
});
// Create multiple documents
User.create(
[
{ name: "Jane Doe", email: "jane@example.com", age: 25 },
{ name: "Bob Smith", email: "bob@example.com", age: 35 },
],
(err, users) => {
if (err) return console.error(err);
console.log("Users created:", users);
}
);
// Find all documents
User.find({}, (err, users) => {
if (err) return console.error(err);
console.log("All users:", users);
});
// Find documents with specific criteria
User.find({ age: { $gte: 18 } }, (err, users) => {
if (err) return console.error(err);
console.log("Adult users:", users);
});
// Find one document
User.findOne({ email: "john@example.com" }, (err, user) => {
if (err) return console.error(err);
console.log("Found user:", user);
});
// Find by ID
User.findById("5f7c3b3f9d3e2a1234567890", (err, user) => {
if (err) return console.error(err);
console.log("User by ID:", user);
});
// Update one document
User.updateOne({ name: "John Doe" }, { age: 31 }, (err, result) => {
if (err) return console.error(err);
console.log("Update result:", result);
});
// Update multiple documents
User.updateMany({ age: { $lt: 18 } }, { isMinor: true }, (err, result) => {
if (err) return console.error(err);
console.log("Update result:", result);
});
// Find and update
User.findOneAndUpdate(
{ email: "john@example.com" },
{ $inc: { age: 1 } },
{ new: true },
(err, updatedUser) => {
if (err) return console.error(err);
console.log("Updated user:", updatedUser);
}
);
// Delete one document
User.deleteOne({ name: "John Doe" }, (err) => {
if (err) return console.error(err);
console.log("User deleted");
});
// Delete multiple documents
User.deleteMany({ age: { $lt: 18 } }, (err) => {
if (err) return console.error(err);
console.log("Minor users deleted");
});
// Find and delete
User.findOneAndDelete({ email: "john@example.com" }, (err, deletedUser) => {
if (err) return console.error(err);
console.log("Deleted user:", deletedUser);
});
Mongoose provides a rich query API.
// Basic querying
User.find({ age: { $gte: 18 } })
.sort({ name: 1 })
.limit(10)
.select("name email")
.exec((err, users) => {
if (err) return console.error(err);
console.log("Adult users:", users);
});
// Chaining queries
User.find({ isActive: true })
.where("age")
.gte(18)
.lte(65)
.where("email")
.ne(null)
.limit(50)
.sort("-lastLogin")
.select("name email")
.exec((err, users) => {
if (err) return console.error(err);
console.log("Active adult users:", users);
});
// Using query builders
const query = User.find({ isActive: true });
query.where("age").gte(18).lte(65);
query.where("email").ne(null);
query.limit(50).sort("-lastLogin").select("name email");
query.exec((err, users) => {
if (err) return console.error(err);
console.log("Active adult users:", users);
});
// Using $or operator
User.find(
{
$or: [{ age: { $lt: 18 } }, { age: { $gt: 65 } }],
},
(err, users) => {
if (err) return console.error(err);
console.log("Users not of working age:", users);
}
);
// Using regex
User.find({ name: /^John/ }, (err, users) => {
if (err) return console.error(err);
console.log("Users with names starting with John:", users);
});
Population is the process of automatically replacing specified paths in the document with document(s) from other collection(s).
// Define schemas with references
const authorSchema = new Schema({
name: String,
bio: String,
});
const bookSchema = new Schema({
title: String,
author: { type: Schema.Types.ObjectId, ref: "Author" },
});
const Author = mongoose.model("Author", authorSchema);
const Book = mongoose.model("Book", bookSchema);
// Create an author and a book
const author = new Author({ name: "John Doe", bio: "A prolific writer" });
author.save((err, savedAuthor) => {
if (err) return console.error(err);
const book = new Book({ title: "My Great Novel", author: savedAuthor._id });
book.save((err, savedBook) => {
if (err) return console.error(err);
console.log("Book saved:", savedBook);
});
});
// Populate the author when querying for books
Book.findOne({ title: "My Great Novel" })
.populate("author")
.exec((err, book) => {
if (err) return console.error(err);
console.log("Book with author details:", book);
});
// Populate specific fields
Book.findOne({ title: "My Great Novel" })
.populate("author", "name")
.exec((err, book) => {
if (err) return console.error(err);
console.log("Book with author name:", book);
});
// Nested population
const publisherSchema = new Schema({
name: String,
location: String,
});
const bookSchema = new Schema({
title: String,
author: { type: Schema.Types.ObjectId, ref: "Author" },
publisher: { type: Schema.Types.ObjectId, ref: "Publisher" },
});
const Publisher = mongoose.model("Publisher", publisherSchema);
const Book = mongoose.model("Book", bookSchema);
Book.findOne({ title: "My Great Novel" })
.populate({
path: "author",
populate: { path: "publisher" },
})
.exec((err, book) => {
if (err) return console.error(err);
console.log("Book with author and publisher details:", book);
});
Middleware (pre and post hooks) are functions which are passed control during execution of asynchronous functions.
// Pre-save middleware
userSchema.pre("save", function (next) {
// 'this' refers to the document being saved
if (this.isModified("password")) {
this.password = hashPassword(this.password);
}
next();
});
// Post-save middleware
userSchema.post("save", function (doc, next) {
console.log("%s has been saved", doc._id);
next();
});
// Pre-find middleware
userSchema.pre("find", function () {
// 'this' refers to the query
this.start = Date.now();
});
userSchema.post("find", function (result) {
console.log("Query took %d milliseconds", Date.now() - this.start);
});
// Error handling middleware
userSchema.post("save", function (error, doc, next) {
if (error.name === "MongoError" && error.code === 11000) {
next(new Error("There was a duplicate key error"));
} else {
next(error);
}
});
Mongoose provides built-in and custom validators.
const userSchema = new Schema({
name: {
type: String,
required: true,
minlength: 2,
maxlength: 50,
},
email: {
type: String,
required: true,
unique: true,
lowercase: true,
validate: {
validator: function (v) {
return /^w+([.-]?w+)*@w+([.-]?w+)*(.w{2,3})+$/.test(v);
},
message: (props) => `${props.value} is not a valid email address!`,
},
},
age: {
type: Number,
min: [18, "Must be at least 18 years old"],
max: [120, "Age seems unrealistic"],
},
interests: {
type: [String],
validate: {
validator: function (v) {
return v && v.length > 0;
},
message: "A user must have at least one interest",
},
},
});
// Custom async validator
userSchema.path("email").validate(async function (value) {
const emailCount = await mongoose.models.User.countDocuments({
email: value,
});
return !emailCount;
}, "Email already exists");
// Using validate()
const user = new User({
name: "J",
email: "invalid-email",
age: 15,
interests: [],
});
user.validate((err) => {
if (err) console.log(err.message);
});
Indexes support the efficient execution of queries in MongoDB.
const userSchema = new Schema({
name: String,
email: { type: String, unique: true },
createdAt: Date,
});
// Single field index
userSchema.index({ name: 1 });
// Compound index
userSchema.index({ name: 1, createdAt: -1 });
// Text index
userSchema.index({ name: "text", email: "text" });
// Geospatial index
const locationSchema = new Schema({
name: String,
location: {
type: { type: String, default: "Point" },
coordinates: [Number],
},
});
locationSchema.index({ location: "2dsphere" });
// Creating indexes
mongoose.connect("mongodb://localhost/test", function (error) {
if (error) console.error(error);
else console.log("connected");
User.createIndexes(function (error) {
if (error) console.error(error);
else console.log("indexes created");
});
});
Virtuals are document properties that you can get and set but that do not get persisted to MongoDB.
const personSchema = new Schema({
firstName: String,
lastName: String,
});
// Virtual for full name
personSchema
.virtual("fullName")
.get(function () {
return this.firstName + " " + this.lastName;
})
.set(function (v) {
this.firstName = v.substr(0, v.indexOf(" "));
this.lastName = v.substr(v.indexOf(" ") + 1);
});
const Person = mongoose.model("Person", personSchema);
const person = new Person({
firstName: "John",
lastName: "Doe",
});
console.log(person.fullName); // 'John Doe'
person.fullName = "Jane Smith";
console.log(person.firstName); // 'Jane'
console.log(person.lastName); // 'Smith'
Plugins are a tool for reusing logic in multiple schemas.
// Define a plugin
const lastModifiedPlugin = function (schema, options) {
schema.add({ lastMod: Date });
schema.pre("save", function (next) {
this.lastMod = new Date();
next();
});
if (options && options.index) {
schema.path("lastMod").index(options.index);
}
};
// Use the plugin
const userSchema = new Schema({ name: String, email: String });
userSchema.plugin(lastModifiedPlugin, { index: true });
// Alternatively, apply the plugin to all schemas
mongoose.plugin(lastModifiedPlugin);
Transactions let you execute multiple operations in isolation and potentially undo all the operations if one of them fails.
const session = await mongoose.startSession();
session.startTransaction();
try {
const opts = { session };
const A = await Account.findOne({ name: "A" }, null, opts);
const B = await Account.findOne({ name: "B" }, null, opts);
A.balance -= 100;
B.balance += 100;
await A.save(opts);
await B.save(opts);
await session.commitTransaction();
session.endSession();
} catch (error) {
await session.abortTransaction();
session.endSession();
throw error;
}
Proper error handling is crucial for robust applications. Here are some ways to handle errors in Mongoose:
// Using callbacks
User.findById(id, (err, user) => {
if (err) {
if (err.name === "CastError") {
return console.error("Invalid ID");
}
return console.error(err);
}
console.log(user);
});
// Using promises
User.findById(id)
.then((user) => {
console.log(user);
})
.catch((err) => {
if (err.name === "CastError") {
console.error("Invalid ID");
} else {
console.error(err);
}
});
// Using async/await
async function findUser(id) {
try {
const user = await User.findById(id);
console.log(user);
} catch (err) {
if (err.name === "CastError") {
console.error("Invalid ID");
} else {
console.error(err);
}
}
}
// Global error handler for Mongoose
mongoose.connection.on("error", (err) => {
console.error("MongoDB connection error:", err);
});
// Custom error handling middleware (for Express.js)
app.use((err, req, res, next) => {
if (err instanceof mongoose.Error.ValidationError) {
return res.status(400).json({ error: err.message });
}
if (err.name === "MongoError" && err.code === 11000) {
return res.status(409).json({ error: "Duplicate key error" });
}
next(err);
});
Here are some best practices to follow when using Mongoose:
const userSchema = new Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
age: { type: Number, min: 18 },
});
userSchema.path("email").validate(function (email) {
return /^[^s@]+@[^s@]+.[^s@]+$/.test(email);
}, "Invalid email format");
userSchema.pre("save", function (next) {
this.updatedAt = Date.now();
next();
});
userSchema.index({ email: 1 }, { unique: true });
User.find()
.lean()
.exec((err, users) => {
console.log(users); // Plain JavaScript objects
});
User.find({}, "name email", (err, users) => {
console.log(users); // Only name and email fields
});
Post.find()
.populate("author")
.exec((err, posts) => {
console.log(posts); // Posts with author details
});
User.findById(id)
.then((user) => {
// Handle success
})
.catch((err) => {
// Handle error
});
mongoose
.connect(uri, { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => console.log("Connected to MongoDB"))
.catch((err) => console.error("Could not connect to MongoDB", err));
const session = await mongoose.startSession();
session.startTransaction();
try {
// Perform operations
await session.commitTransaction();
} catch (error) {
await session.abortTransaction();
} finally {
session.endSession();
}
2024 © All rights reserved - buraxta.com