MongoDB with Node.js Complete Guide
What is MongoDB?
MongoDB is a popular NoSQL, document-oriented database that provides high performance, high availability, and easy scalability. Unlike traditional SQL databases that use tables and rows, MongoDB stores data in flexible, JSON-like documents.
Why MongoDB with Node.js?
- JSON-like Documents: Natural fit for JavaScript applications
- Flexible Schema: Easy to evolve your data model
- Scalability: Horizontal scaling capabilities
- Rich Query Language: Powerful querying capabilities
- JavaScript Integration: Seamless integration with Node.js
Setting Up MongoDB
Option 1: MongoDB Atlas (Cloud)
1. Sign up at MongoDB Atlas
2. Create a free cluster
3. Get your connection string
Option 2: Local MongoDB
1. Download MongoDB from mongodb.com
2. Install and start MongoDB service
Connecting to MongoDB with Node.js
Install MongoDB Driver
npm install mongodb
Basic Connection
const { MongoClient } = require('mongodb');
const uri = 'mongodb://localhost:27017';
const client = new MongoClient(uri);
async function connectToMongo() {
try {
await client.connect();
console.log('Connected to MongoDB');
return client.db('myapp');
} catch (error) {
console.error('MongoDB connection error:', error);
}
}
connectToMongo();
Using Mongoose ODM
Mongoose is an Object Data Modeling (ODM) library for MongoDB and Node.js. It provides a straightforward schema-based solution to model your application data.
Install Mongoose
npm install mongoose
Connect to MongoDB with Mongoose
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 with Mongoose');
});
Creating Schemas and Models
Define a Schema
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true
},
age: {
type: Number,
min: 0,
max: 120
},
createdAt: {
type: Date,
default: Date.now
},
isActive: {
type: Boolean,
default: true
}
});
Create a Model
const User = mongoose.model('User', userSchema);
// Now you can use the User model to interact with the database
CRUD Operations with Mongoose
Create (Insert)
// Create a new user
async function createUser(userData) {
try {
const user = new User(userData);
const savedUser = await user.save();
console.log('User created:', savedUser);
return savedUser;
} catch (error) {
console.error('Error creating user:', error);
}
}
// Usage
createUser({
name: 'John Doe',
email: 'john@example.com',
age: 30
});
Read (Find)
// Find all users
async function getAllUsers() {
try {
const users = await User.find();
return users;
} catch (error) {
console.error('Error finding users:', error);
}
}
// Find user by ID
async function getUserById(userId) {
try {
const user = await User.findById(userId);
return user;
} catch (error) {
console.error('Error finding user:', error);
}
}
// Find with filters
async function findUsersByAge(minAge) {
try {
const users = await User.find({ age: { $gte: minAge } });
return users;
} catch (error) {
console.error('Error finding users:', error);
}
}
// Find one user
async function findUserByEmail(email) {
try {
const user = await User.findOne({ email });
return user;
} catch (error) {
console.error('Error finding user:', error);
}
}
Update
// Update user by ID
async function updateUser(userId, updateData) {
try {
const updatedUser = await User.findByIdAndUpdate(
userId,
updateData,
{ new: true, runValidators: true }
);
return updatedUser;
} catch (error) {
console.error('Error updating user:', error);
}
}
// Update one user
async function updateUserByEmail(email, updateData) {
try {
const result = await User.updateOne(
{ email },
updateData
);
return result;
} catch (error) {
console.error('Error updating user:', error);
}
}
Delete
// Delete user by ID
async function deleteUser(userId) {
try {
const result = await User.findByIdAndDelete(userId);
return result;
} catch (error) {
console.error('Error deleting user:', error);
}
}
// Delete one user
async function deleteUserByEmail(email) {
try {
const result = await User.deleteOne({ email });
return result;
} catch (error) {
console.error('Error deleting user:', error);
}
}
Advanced Querying
Query Operators
// Comparison operators
const users = await User.find({
age: { $gte: 18, $lt: 65 }, // age >= 18 AND age < 65
email: { $regex: /@gmail\.com$/i } // ends with @gmail.com
});
// Logical operators
const activeUsers = await User.find({
$and: [
{ isActive: true },
{ age: { $gte: 18 } }
]
});
// Array operators
const users = await User.find({
tags: { $in: ['developer', 'javascript'] }
});
Sorting and Limiting
// Sort by name (ascending)
const users = await User.find().sort({ name: 1 });
// Sort by age (descending) and limit results
const topUsers = await User.find()
.sort({ age: -1 })
.limit(10);
// Skip and limit for pagination
const page = 2;
const limit = 10;
const skip = (page - 1) * limit;
const users = await User.find()
.skip(skip)
.limit(limit);
Aggregation Pipeline
// Group users by age and count
const ageStats = await User.aggregate([
{
$group: {
_id: '$age',
count: { $sum: 1 },
names: { $push: '$name' }
}
},
{
$sort: { count: -1 }
}
]);
// Average age of active users
const avgAge = await User.aggregate([
{ $match: { isActive: true } },
{
$group: {
_id: null,
averageAge: { $avg: '$age' },
totalUsers: { $sum: 1 }
}
}
]);
Data Modeling Best Practices
Embedding vs Referencing
// Embedding (one-to-one relationship)
const userSchema = new mongoose.Schema({
name: String,
email: String,
address: {
street: String,
city: String,
country: String
}
});
// Referencing (one-to-many relationship)
const authorSchema = new mongoose.Schema({
name: String,
email: String,
books: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Book' }]
});
Indexing for Performance
// Single field index
userSchema.index({ email: 1 });
// Compound index
userSchema.index({ name: 1, age: -1 });
// Text index for search
userSchema.index({ name: 'text', bio: 'text' });
// Unique index
userSchema.index({ email: { unique: true } });
Validation
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Name is required'],
minlength: [2, 'Name must be at least 2 characters'],
maxlength: 50
},
email: {
type: String,
required: true,
unique: true,
validate: {
validator: function(v) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
},
message: 'Invalid email format'
}
},
age: {
type: Number,
min: [0, 'Age cannot be negative'],
max: [120, 'Age cannot be more than 120']
}
});
Middleware
// Pre-save middleware
userSchema.pre('save', function(next) {
// Convert email to lowercase
this.email = this.email.toLowerCase();
next();
});
// Post-save middleware
userSchema.post('save', function(doc) {
console.log('User saved:', doc.name);
});
Next Steps
- Build REST APIs with Express and MongoDB
- Implement authentication and authorization
- Optimize database performance
- Deploy your MongoDB application
Conclusion
MongoDB and Node.js make an excellent combination for building modern applications. With Mongoose, you get the benefits of schema validation, type casting, and business logic hooks while maintaining the flexibility of a NoSQL database.