When developing applications using Express.js with a MongoDB database, one of the most efficient and popular tools available is Mongoose, an Object Data Modeling (ODM) library for MongoDB and Node.js. Mongoose provides a straightforward, schema-based solution to model your application data, allowing you to interact with MongoDB using JavaScript objects. It simplifies data validation, casting, querying, and business logic management, making it an essential tool for developers working with MongoDB.
Mongoose is an ODM (Object Data Modeling) library for MongoDB and Node.js. It provides a schema-based structure to the MongoDB collections and allows you to interact with MongoDB using models defined by schemas. Mongoose supports built-in data validation, type casting, query building, and more.
Before we can use Mongoose in our Express.js application, we need to install the necessary packages.
Installation:
npm install express mongoose
To connect your Express.js application to a MongoDB database using Mongoose, you need to establish a connection when the server starts.
app.js
const express = require('express');
const mongoose = require('mongoose');
const app = express();
// Connect to MongoDB
mongoose.connect('mongodb://127.0.0.1:27017/mydatabase', {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log('MongoDB connected...'))
.catch(err => console.log(err));
app.use(express.json());
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
mongodb://127.0.0.1:27017/mydatabase
points to a MongoDB instance running on localhost with a database named mydatabase
.MongoDB connected...
in the console.A schema in Mongoose defines the structure of documents within a MongoDB collection. It determines the shape of the documents by specifying the fields, their data types, default values, and validation requirements.
To define a schema, you use the mongoose.Schema
class, which allows you to specify the fields and their types.
models/User.js
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
age: {
type: Number,
min: 0,
},
createdAt: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model('User', UserSchema);
User
collection.A model in Mongoose is a wrapper for the schema and provides an interface to interact with the MongoDB collection.
models/User.js
(continued)
module.exports = mongoose.model('User', UserSchema);
User
based on the UserSchema
. The User
model represents the users
collection in MongoDB.To insert a new document into the MongoDB collection, you can use the save()
method provided by the Mongoose model.
app.js
(continued)
const User = require('./models/User');
app.post('/users', async (req, res) => {
try {
const user = new User(req.body);
await user.save();
res.status(201).json(user);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
User
model with the data from the request body./users
with user data (e.g., { name: 'John', email: 'john@example.com', age: 25 }
), a new document is inserted into the users
collection, and the created user is returned in the response.To retrieve documents from the MongoDB collection, you can use the find()
and findOne()
methods.
app.js
(continued)
app.get('/users', async (req, res) => {
try {
const users = await User.find();
res.json(users);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.get('/users/:id', async (req, res) => {
try {
const user = await User.findById(req.params.id);
if (user) {
res.json(user);
} else {
res.status(404).json({ error: 'User not found' });
}
} catch (err) {
res.status(500).json({ error: err.message });
}
});
users
collection._id
field./users
returns all users in the collection./users/:id
returns the user with the specified ID or a 404 error if not found.To update an existing document, you can use the findByIdAndUpdate()
method.
app.js
(continued)
app.put('/users/:id', async (req, res) => {
try {
const user = await User.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true,
});
if (user) {
res.json(user);
} else {
res.status(404).json({ error: 'User not found' });
}
} catch (err) {
res.status(400).json({ error: err.message });
}
});
new: true
option ensures that the updated document is returned, and runValidators: true
applies schema validation during the update./users/:id
, the specified user document is updated, and the updated document is returned.To delete a document from the collection, use the findByIdAndDelete()
method.
app.js
(continued)
app.delete('/users/:id', async (req, res) => {
try {
const user = await User.findByIdAndDelete(req.params.id);
if (user) {
res.json({ message: 'User deleted' });
} else {
res.status(404).json({ error: 'User not found' });
}
} catch (err) {
res.status(500).json({ error: err.message });
}
});
/users/:id
deletes the specified user document and returns a confirmation message.Mongoose allows you to enforce data validation rules directly in the schema definition.
Example: Adding validation to the User
schema.
models/User.js
(updated)
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Name is required'],
minlength: [3, 'Name must be at least 3 characters long'],
},
email: {
type: String,
required: [true, 'Email is required'],
unique: true,
match: [/.+\@.+\..+/, 'Please enter a valid email address'],
},
age: {
type: Number,
min: [0, 'Age cannot be negative'],
max: [120, 'Age cannot exceed 120 years'],
},
});
name
field is provided.name
.Mongoose supports middleware functions (also known as hooks) that can be executed before or after certain operations, like save
, remove
, or update
.
Example: Adding a pre-save hook to hash a user’s password.
models/User.js
(updated)
const bcrypt = require('bcrypt');
UserSchema.pre('save', async function (next) {
if (!this.isModified('password')) return next();
try {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
} catch (err) {
next(err);
}
});
Virtuals are document properties that you can get and set but do not get persisted to MongoDB.
Example: Creating a virtual field for the user’s full name.
models/User.js
(updated)
UserSchema.virtual('fullName').get(function () {
return `${this.firstName} ${this.lastName}`;
});
fullName
that concatenates firstName
and lastName
.
const user = await User.findById(id);
console.log(user.fullName); // Outputs: 'John Doe'
Mongoose provides a powerful querying API that allows you to build complex queries.
Example: Finding users who are older than 18 and sorting them by age.
app.js
(continued)
app.get('/users/adults', async (req, res) => {
try {
const users = await User.find({ age: { $gt: 18 } }).sort('age');
res.json(users);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
/users/adults
returns a list of users older than 18, sorted by age.Mongoose supports MongoDB’s aggregation framework, which allows for performing advanced data processing.
Example: Aggregating users by age group.
app.js
(continued)
app.get('/users/age-groups', async (req, res) => {
try {
const groups = await User.aggregate([
{ $match: { age: { $exists: true } } },
{ $group: { _id: { $floor: { $divide: ['$age', 10] } }, count: { $sum: 1 } } },
{ $sort: { _id: 1 } },
]);
res.json(groups);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
users
collection.age
field exists./users/age-groups
returns a list of age groups and the count of users in each group.In this chapter, we explored how to use Mongoose ODM with Express.js to interact with a MongoDB database. By mastering these concepts, you'll be able to efficiently manage your MongoDB database within an Express.js application, ensuring that your data is well-structured, validated, and easily accessible. Happy coding !❤️