MongoDB Guide: Fundamentals to Advanced Usage
Introduction to MongoDB
MongoDB is a popular NoSQL database that stores data in flexible, JSON-like documents. Unlike traditional relational databases that use tables and rows, MongoDB uses collections and documents, making it ideal for handling unstructured or semi-structured data.
Key Features
- Document-oriented: Stores data in flexible, JSON-like documents
- Schema-less: No predefined schema required
- Scalable: Horizontal scaling through sharding
- High performance: Indexing, replication, and aggregation capabilities
- Flexible query language: Rich query capabilities
MongoDB Structure Hierarchy
Database → Collections → Documents → Fields
- Database: Container for collections
- Collection: Group of MongoDB documents (similar to a table in RDBMS)
- Document: Set of key-value pairs stored in BSON format (Binary JSON)
- Field: A key-value pair in a document
BSON Format
MongoDB uses BSON (Binary JSON) for storing documents. BSON extends JSON with additional data types and is optimized for:
- Data traversal
- Encoding/decoding
- Space efficiency
BSON supports data types like Date, ObjectID, Binary, etc. that aren't available in JSON.
Installation and Setup
Installing MongoDB Community Edition
On Ubuntu/Debian:
Reference : https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-ubuntu/
# Import the MongoDB public GPG key
wget -qO - https://www.mongodb.org/static/pgp/server-7.0.asc | sudo apt-key add -
# Create a list file for MongoDB
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
# Update package list
sudo apt-get update
# Install MongoDB
sudo apt-get install -y mongodb-org
# Start MongoDB service
sudo systemctl start mongod
# Enable MongoDB to start on boot
sudo systemctl enable mongod
Verifying Installation
# Check MongoDB version
mongod --version
# Check service status
sudo systemctl status mongod
# List installed MongoDB packages
dpkg -l | grep mongodb
MongoDB Tools
MongoDB Shell (mongosh)
The MongoDB Shell is an interactive JavaScript interface to MongoDB. It's used for querying and updating data as well as performing administrative operations.
# Connect to MongoDB instance
mongosh
MongoDB Compass
Reference : https://www.mongodb.com/docs/compass/current/install/
wget https://downloads.mongodb.com/compass/mongodb-compass_1.45.4_amd64.deb
sudo apt install ./mongodb-compass_1.45.4_amd64.deb
mongodb-compass
MongoDB Compass is a GUI for MongoDB that allows you to:
- Visualize and explore your data
- Run ad hoc queries
- Perform CRUD operations
- View performance metrics
- Create and manage indexes
MongoDB Atlas vs MongoDB Compass
- MongoDB Atlas: Cloud database service for MongoDB, offering automated deployments, backups, and scaling.
- MongoDB Compass: GUI tool for interacting with MongoDB databases, whether they're local or in the cloud.
Basic MongoDB Operations
Connecting to MongoDB
// Connect to local MongoDB instance
mongosh
// Connect to a specific database
mongosh "mongodb://localhost:27017/databaseName"
Database Operations
// Show all databases
show dbs
// Switch to a specific database (creates it if doesn't exist)
use databaseName
// Get current database name
db.getName()
// Drop current database
db.dropDatabase()
Running JavaScript in mongosh
MongoDB shell allows you to execute JavaScript code:
// Define variables
const x = 10;
const y = 5;
print(x + y);
// Create functions
function calculateAverage(arr) {
return arr.reduce((a, b) => a + b, 0) / arr.length;
}
print(calculateAverage([1, 2, 3, 4, 5]));
// Use built-in JS methods
const now = new Date();
print(now);
CRUD Operations (Create, Read, Update, Delete)
Creating Documents
Insert One Document
// Insert a single document
db.collectionName.insertOne({field1: value1, field2: value2})
// Example
db.movies.insertOne({"name": "Foo", "rating": 10})
// View available options
db.movies.insertOne.help()
Insert Multiple Documents
// Create documents
m1 = {"name": "A Foo 1", "rating": 4}
m2 = {"name": "B Foo 2", "rating": 5}
m3 = {"name": "C Foo 3", "rating": 6}
// Insert multiple documents
db.movies.insertMany([m1, m2, m3])
// View available options
db.movies.insertMany.help()
Reading Documents
// Show all collections in the current database
show collections
// Find all documents in a collection
db.movies.find()
// Find documents that match criteria
db.movies.find({"name": "Foo 1"})
// Projection: include only specified fields
db.movies.find({"name": "Foo 1"}, {"name": 1}) // Only return name field
// Projection: exclude specified fields
db.movies.find({"name": "Foo 1"}, {"name": 0}) // Return all fields except name
// Project specific fields for all documents
db.movies.find({}, {"name": 1}) // Return only name field for all documents
// Count documents
db.movies.find().count()
// Limit results
db.movies.find().limit(2) // Return first 2 documents
// Sort results (1 for ascending, -1 for descending)
db.movies.find({}, {"name": 1}).sort({"name": 1}) // Sort by name in ascending order
// Skip results
db.movies.find({}, {"name": 1}).sort({"name": 1}).skip(1) // Skip first document
// Query operators
db.movies.find({"rating": {$lt: 5}}) // rating less than 5
db.movies.find({"rating": {$gt: 5}}) // rating greater than 5
db.movies.find({"rating": {$lte: 5}}) // rating less than or equal to 5
db.movies.find({"rating": {$gte: 5}}) // rating greater than or equal to 5
db.movies.find({"rating": {$ne: 5}}) // rating not equal to 5
db.movies.find({"rating": {$in: [3, 5, 7]}}) // rating is 3, 5, or 7
// Logical operators
db.movies.find({$and: [{"rating": {$gt: 5}}, {"name": /^A/}]}) // rating > 5 AND name starts with "A"
db.movies.find({$or: [{"rating": {$lt: 3}}, {"rating": {$gt: 7}}]}) // rating < 3 OR rating > 7
Updating Documents
// Update a single document
db.movies.updateOne({"name": "Foo 1"}, {$set: {rating: 6}})
// Update multiple documents
db.movies.updateMany({rating: 10}, {$set: {rating: 5}})
// View available options
db.movies.updateOne.help()
db.movies.updateMany.help()
// Update operators
db.movies.updateOne({"name": "Foo 1"}, {$inc: {rating: 1}}) // Increment rating by 1
db.movies.updateOne({"name": "Foo 1"}, {$push: {genres: "Action"}}) // Add to array
db.movies.updateOne({"name": "Foo 1"}, {$pull: {genres: "Comedy"}}) // Remove from array
db.movies.updateOne({"name": "Foo 1"}, {$unset: {director: ""}}) // Remove field
Deleting Documents
// Delete a single document
db.movies.deleteOne({"name": "Foo 1"})
// Delete multiple documents
db.movies.deleteMany({rating: 5})
// View available options
db.movies.deleteOne.help()
db.movies.deleteMany.help()
// Delete all documents in a collection
db.movies.deleteMany({})
Advanced MongoDB Features
Indexes
// Create an index
db.movies.createIndex({"name": 1}) // 1 for ascending, -1 for descending
// Create a compound index
db.movies.createIndex({"name": 1, "rating": -1})
// Create a unique index
db.movies.createIndex({"name": 1}, {unique: true})
// List all indexes in a collection
db.movies.getIndexes()
// Drop an index
db.movies.dropIndex("name_1")
Aggregation Framework
// Simple aggregation example: group and count
db.movies.aggregate([
{$group: {_id: "$rating", count: {$sum: 1}}}
])
// Multi-stage aggregation pipeline
db.movies.aggregate([
{$match: {rating: {$gte: 5}}}, // Filter documents
{$group: {_id: "$genre", avgRating: {$avg: "$rating"}}}, // Group and calculate average
{$sort: {avgRating: -1}} // Sort by average rating descending
])
Working with JavaScript in MongoDB Shell
// Convert find cursor to an array for processing
const results = db.movies.find().toArray()
// Display results in a table format
console.table(results)
// Process results with JavaScript
results.forEach(movie => {
print(`Movie: ${movie.name}, Rating: ${movie.rating}`)
})
// Calculate average rating
const avg = results.reduce((acc, movie) => acc + movie.rating, 0) / results.length
print(`Average Rating: ${avg}`)
Data Import/Export
# Import JSON data
mongoimport --db=myDB --collection=movies --file=movies.json
# Export to JSON
mongoexport --db=myDB --collection=movies --out=movies.json
# Import BSON data (from mongodump)
mongorestore --db=myDB dump/myDB/
# Export to BSON (for backup)
mongodump --db=myDB
MongoDB Security Best Practices
- Enable authentication
- Create specific users with appropriate privileges
- Use TLS/SSL for encrypted connections
- Enable role-based access control
- Regularly update MongoDB to the latest version
- Use firewalls to restrict access to MongoDB ports
- Audit database access and operations
Enable Authentication
// Create admin user
use admin
db.createUser({
user: "adminUser",
pwd: "securePassword",
roles: [{ role: "userAdminAnyDatabase", db: "admin" }]
})
// Create regular user
use myDatabase
db.createUser({
user: "appUser",
pwd: "appPassword",
roles: [{ role: "readWrite", db: "myDatabase" }]
})
MongoDB Atlas
MongoDB Atlas is the cloud-hosted database service provided by MongoDB, Inc. It offers:
- Automated deployment and scaling
- Backup and restoration
- Monitoring and alerts
- Security features (encryption, VPC peering, etc.)
- Global clusters for low-latency access worldwide
Setting up MongoDB Atlas
- Create an account on MongoDB Atlas
- Create a new cluster (free tier available)
- Configure network access (whitelist IP addresses)
- Create database users
- Connect to your cluster via connection string
Tips and Best Practices
-
Design your schema for your queries: Unlike relational databases, MongoDB performs best when your schema design matches your access patterns.
-
Use appropriate indexing: Create indexes for frequently queried fields, but be mindful that each index adds overhead to write operations.
-
Embed or reference: Choose between embedding related data or using references based on your access patterns and the size/volatility of the data.
-
Limit document size: Keep documents under the 16MB limit and consider references for larger data.
-
Use the aggregation framework: For complex queries involving multiple operations.
-
Implement data validation: Use JSON Schema validation to enforce document structure.
-
Monitor performance: Use MongoDB's built-in tools to identify slow queries and optimize them.
-
Use appropriate write concern: Balance between performance and data durability based on your application needs.
Common MongoDB Commands Cheat Sheet
// Database Operations
show dbs - List all databases
use <db> - Switch to database (creates if not exists)
db - Show current database
db.dropDatabase() - Delete current database
// Collection Operations
show collections - List all collections in current database
db.createCollection("name") - Create a new collection
db.collection.drop() - Delete a collection
// CRUD Operations
db.collection.insertOne({}) - Insert one document
db.collection.insertMany([{}]) - Insert multiple documents
db.collection.find() - Find all documents
db.collection.find({}) - Find documents matching criteria
db.collection.updateOne() - Update one document
db.collection.updateMany() - Update multiple documents
db.collection.deleteOne() - Delete one document
db.collection.deleteMany() - Delete multiple documents
// Aggregation
db.collection.aggregate([]) - Run aggregation pipeline
// Administration
db.getUsers() - List all users in current database
db.createUser() - Create new user
db.runCommand({}) - Run database command
Mongoose: ODM for MongoDB
Mongoose is an Object Data Modeling (ODM) library for Node.js and MongoDB. It provides a higher-level abstraction layer on top of MongoDB's native driver, making it easier to work with MongoDB in a Node.js environment.
Key Features of Mongoose
- Schema Definition: Define structured schemas for your MongoDB documents
- Data Validation: Enforce data integrity with built-in and custom validators
- Middleware: Implement pre and post hooks for database operations
- Query Building: Create complex queries with a chainable API
- Relationship Management: Define and work with relationships between documents
- Type Casting: Automatically convert data types as specified in the schema
- Business Logic Hooks: Attach methods to models for document instances
Installing Mongoose
npm install mongoose
Connecting to MongoDB with Mongoose
const mongoose = require('mongoose');
// Connect to local MongoDB
mongoose.connect('mongodb://localhost:27017/mydatabase', {
useNewUrlParser: true,
useUnifiedTopology: true
});
// Connection events
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
console.log("Connected to MongoDB!");
});
Creating a Schema
const mongoose = require('mongoose');
const { Schema } = mongoose;
// Define a schema
const movieSchema = new Schema({
name: {
type: String,
required: true,
trim: true
},
rating: {
type: Number,
min: 0,
max: 10,
default: 0
},
genre: [String],
releaseDate: Date,
director: {
name: String,
awards: Number
},
isAvailable: Boolean,
createdAt: {
type: Date,
default: Date.now
}
});
// Create a model from the schema
const Movie = mongoose.model('Movie', movieSchema);
Schema Data Types
Mongoose supports various data types:
- String
- Number
- Date
- Buffer
- Boolean
- Mixed
- ObjectId
- Array
- Decimal128
- Map
Schema Validation
const userSchema = new Schema({
username: {
type: String,
required: [true, 'Username is required'],
minlength: [4, 'Username must be at least 4 characters'],
maxlength: [20, 'Username cannot exceed 20 characters'],
unique: true,
trim: true
},
email: {
type: String,
required: true,
unique: true,
validate: {
validator: function(v) {
return /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(v);
},
message: props => `${props.value} is not a valid email!`
}
},
password: {
type: String,
required: true,
minlength: 8
},
age: {
type: Number,
min: [18, 'Must be at least 18 years old'],
max: [100, 'Age cannot exceed 100']
}
});
CRUD Operations with Mongoose
Creating Documents
// Create a new document
const movie = new Movie({
name: 'Inception',
rating: 8.8,
genre: ['Sci-Fi', 'Action'],
releaseDate: new Date('2010-07-16'),
director: {
name: 'Christopher Nolan',
awards: 11
},
isAvailable: true
});
// Save the document
movie.save()
.then(doc => {
console.log('Movie saved:', doc);
})
.catch(err => {
console.error('Error saving movie:', err);
});
// Alternative: create method
Movie.create({
name: 'The Matrix',
rating: 8.7,
genre: ['Sci-Fi', 'Action']
})
.then(doc => {
console.log('Movie created:', doc);
})
.catch(err => {
console.error('Error creating movie:', err);
});
Reading Documents
// Find all documents
Movie.find()
.then(movies => {
console.log('All movies:', movies);
})
.catch(err => {
console.error('Error finding movies:', err);
});
// Find with criteria
Movie.find({ rating: { $gte: 8 } })
.then(movies => {
console.log('Highly rated movies:', movies);
});
// Find one document
Movie.findOne({ name: 'Inception' })
.then(movie => {
console.log('Found movie:', movie);
});
// Find by ID
Movie.findById('60a1e2c9c8c8e52a4c8e2c9c')
.then(movie => {
console.log('Movie by ID:', movie);
});
// Query building
Movie.find()
.select('name rating') // Select only name and rating fields
.where('rating').gte(8) // Rating greater than or equal to 8
.where('genre').in(['Action', 'Adventure']) // Genre is Action or Adventure
.limit(5) // Limit to 5 results
.sort('-rating') // Sort by rating descending
.exec() // Execute the query
.then(movies => {
console.log('Query result:', movies);
});
Updating Documents
// Update one document
Movie.updateOne(
{ name: 'Inception' },
{ rating: 9.0 }
)
.then(result => {
console.log('Update result:', result);
});
// Find and update (returns updated document)
Movie.findOneAndUpdate(
{ name: 'The Matrix' },
{ $push: { genre: 'Cyberpunk' } },
{ new: true } // Return the updated document
)
.then(updatedMovie => {
console.log('Updated movie:', updatedMovie);
});
// Update multiple documents
Movie.updateMany(
{ rating: { $lt: 5 } },
{ isAvailable: false }
)
.then(result => {
console.log('Bulk update result:', result);
});
Deleting Documents
// Delete one document
Movie.deleteOne({ name: 'Inception' })
.then(result => {
console.log('Delete result:', result);
});
// Find and delete (returns deleted document)
Movie.findOneAndDelete({ rating: { $lt: 5 } })
.then(deletedMovie => {
console.log('Deleted movie:', deletedMovie);
});
// Delete multiple documents
Movie.deleteMany({ isAvailable: false })
.then(result => {
console.log('Bulk delete result:', result);
});
Middleware (Hooks)
Mongoose middleware (pre and post hooks) functions let you execute code before or after specific operations.
// Pre-save hook
movieSchema.pre('save', function(next) {
// 'this' refers to the document being saved
console.log(`Saving movie: ${this.name}`);
// You can modify the document here
if (this.rating > 9.5) {
this.genre.push('Masterpiece');
}
next(); // Call next to continue with the save operation
});
// Post-save hook
movieSchema.post('save', function(doc, next) {
console.log(`Movie saved: ${doc.name}`);
next();
});
// Pre-find hook
movieSchema.pre('find', function() {
// 'this' refers to the query object
this.start = Date.now();
});
// Post-find hook
movieSchema.post('find', function(docs) {
console.log(`Query took ${Date.now() - this.start}ms`);
});
Instance and Static Methods
// Instance method (available on document instances)
movieSchema.methods.getSummary = function() {
return `${this.name} (${this.rating}/10)`;
};
// Use instance method
const movie = await Movie.findOne({ name: 'Inception' });
console.log(movie.getSummary()); // "Inception (8.8/10)"
// Static method (available on the model)
movieSchema.statics.findByGenre = function(genre) {
return this.find({ genre: genre });
};
// Use static method
const actionMovies = await Movie.findByGenre('Action');
Virtual Properties
Virtual properties are not stored in MongoDB but computed from other fields.
// Define a virtual property
movieSchema.virtual('nameAndRating').get(function() {
return `${this.name} (${this.rating}/10)`;
});
// Use virtual property
const movie = await Movie.findOne();
console.log(movie.nameAndRating); // "Inception (8.8/10)"
Populate (References)
Mongoose can populate references to other documents.
// Define schemas with references
const directorSchema = new Schema({
name: String,
bio: String,
birthDate: Date
});
const Director = mongoose.model('Director', directorSchema);
const filmSchema = new Schema({
title: String,
director: {
type: Schema.Types.ObjectId,
ref: 'Director' // Reference to Director model
}
});
const Film = mongoose.model('Film', filmSchema);
// Create a director and a film
const director = await Director.create({
name: 'Christopher Nolan',
bio: 'British-American film director...',
birthDate: new Date('1970-07-30')
});
await Film.create({
title: 'Interstellar',
director: director._id // Store only the reference ID
});
// Query with populate to get director details
const films = await Film.find().populate('director');
console.log(films[0].director.name); // "Christopher Nolan"
Using Async/Await with Mongoose
Modern Node.js applications typically use async/await with Mongoose:
// Connect to MongoDB
async function connectToMongoDB() {
try {
await mongoose.connect('mongodb://localhost:27017/mydatabase', {
useNewUrlParser: true,
useUnifiedTopology: true
});
console.log('Connected to MongoDB');
} catch (error) {
console.error('MongoDB connection error:', error);
}
}
// CRUD operations with async/await
async function createMovie() {
try {
const movie = await Movie.create({
name: 'The Dark Knight',
rating: 9.0,
genre: ['Action', 'Crime', 'Drama']
});
console.log('Movie created:', movie);
return movie;
} catch (error) {
console.error('Error creating movie:', error);
}
}
async function findMovies() {
try {
const movies = await Movie.find({ rating: { $gte: 8 } })
.select('name rating')
.limit(5);
console.log('Found movies:', movies);
return movies;
} catch (error) {
console.error('Error finding movies:', error);
}
}
// Error handling with async/await
async function safeOperation() {
try {
await connectToMongoDB();
const movie = await createMovie();
await movie.remove();
console.log('Operation completed successfully');
} catch (error) {
console.error('Operation failed:', error);
} finally {
// Clean up resources if needed
}
}
Schema Options
const movieSchema = new Schema({
name: String,
rating: Number
}, {
timestamps: true, // Adds createdAt and updatedAt fields
collection: 'films', // Custom collection name
versionKey: '_version', // Custom version key (default: __v)
id: false, // Disable virtual id getter
toJSON: { virtuals: true }, // Include virtuals when converting to JSON
toObject: { virtuals: true } // Include virtuals when converting to object
});
Mongoose Best Practices
-
Always handle connection errors: Set up error listeners on the mongoose connection.
-
Use schema validation: Define strict schemas with validation rules.
-
Use middleware wisely: Avoid complex logic in middleware that could impact performance.
-
Lean queries for better performance: Use
.lean()
when you only need data and not full Mongoose documents.const movies = await Movie.find().lean();
-
Index fields you query frequently: Add indexes to fields used in queries.
movieSchema.index({ name: 1 }); movieSchema.index({ rating: -1, name: 1 });
-
Use projection to limit fields returned: Only request the fields you need.
const movies = await Movie.find().select('name rating');
-
Batch operations for better performance: Use
insertMany
,updateMany
, ordeleteMany
for bulk operations. -
Be careful with unbounded arrays: Large arrays within documents can degrade performance.
-
Handle duplicate key errors: Implement proper error handling for unique constraint violations.
-
Use transactions for complex operations: For operations that need to be atomic across multiple documents.
Advanced MongoDB Use Cases
- Real-time analytics: Use change streams to track and react to data changes
- Full-text search: Implement text search and text indexing for content-heavy applications
- Geospatial queries: Build location-based services with geospatial indexes
- Time series data: Store and query time series data with time series collections
- Graph relationships: Model hierarchical data and graph relationships
- Data archiving: Implement data lifecycle management with TTL indexes