MongoDB with Node.js: Complete Database Guide
Master MongoDB with Node.js and Mongoose. Learn CRUD operations, schema design, indexing, aggregation pipelines, and production best practices.
Moshiour Rahman
Advertisement
What is MongoDB?
MongoDB is a document-oriented NoSQL database that stores data in flexible, JSON-like documents. It’s designed for scalability and developer productivity.
MongoDB vs SQL
| SQL Database | MongoDB |
|---|---|
| Tables | Collections |
| Rows | Documents |
| Columns | Fields |
| Joins | Embedded documents / References |
| Schema required | Schema optional |
Getting Started
Setup
npm init -y
npm install mongodb mongoose
npm install -D typescript @types/node ts-node
Native MongoDB Driver
import { MongoClient, ObjectId } from 'mongodb';
const uri = 'mongodb://localhost:27017';
const client = new MongoClient(uri);
async function main() {
try {
await client.connect();
console.log('Connected to MongoDB');
const db = client.db('myapp');
const users = db.collection('users');
// Insert one
const result = await users.insertOne({
name: 'John Doe',
email: 'john@example.com',
createdAt: new Date()
});
console.log(`Inserted: ${result.insertedId}`);
// Find one
const user = await users.findOne({ email: 'john@example.com' });
console.log(user);
} finally {
await client.close();
}
}
main();
Mongoose ODM
Connection Setup
import mongoose from 'mongoose';
const connectDB = async () => {
try {
await mongoose.connect('mongodb://localhost:27017/myapp', {
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000
});
console.log('MongoDB connected');
} catch (error) {
console.error('MongoDB connection error:', error);
process.exit(1);
}
};
// Handle connection events
mongoose.connection.on('disconnected', () => {
console.log('MongoDB disconnected');
});
mongoose.connection.on('error', (err) => {
console.error('MongoDB error:', err);
});
// Graceful shutdown
process.on('SIGINT', async () => {
await mongoose.connection.close();
process.exit(0);
});
export default connectDB;
Schema Definition
import mongoose, { Schema, Document, Model } from 'mongoose';
// Interface for TypeScript
interface IUser extends Document {
name: string;
email: string;
password: string;
age?: number;
role: 'user' | 'admin';
profile: {
bio?: string;
avatar?: string;
social: {
twitter?: string;
github?: string;
};
};
posts: mongoose.Types.ObjectId[];
createdAt: Date;
updatedAt: Date;
}
// Schema definition
const userSchema = new Schema<IUser>({
name: {
type: String,
required: [true, 'Name is required'],
trim: true,
minlength: [2, 'Name must be at least 2 characters'],
maxlength: [50, 'Name cannot exceed 50 characters']
},
email: {
type: String,
required: true,
unique: true,
lowercase: true,
match: [/^\S+@\S+\.\S+$/, 'Invalid email format']
},
password: {
type: String,
required: true,
minlength: 8,
select: false // Don't include in queries by default
},
age: {
type: Number,
min: [0, 'Age cannot be negative'],
max: [150, 'Invalid age']
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user'
},
profile: {
bio: { type: String, maxlength: 500 },
avatar: String,
social: {
twitter: String,
github: String
}
},
posts: [{
type: Schema.Types.ObjectId,
ref: 'Post'
}]
}, {
timestamps: true, // Adds createdAt and updatedAt
toJSON: { virtuals: true },
toObject: { virtuals: true }
});
// Indexes
userSchema.index({ email: 1 });
userSchema.index({ name: 'text', 'profile.bio': 'text' });
// Virtual field
userSchema.virtual('postCount').get(function() {
return this.posts.length;
});
// Pre-save middleware
userSchema.pre('save', async function(next) {
if (this.isModified('password')) {
const bcrypt = require('bcryptjs');
this.password = await bcrypt.hash(this.password, 12);
}
next();
});
// Instance methods
userSchema.methods.comparePassword = async function(candidatePassword: string) {
const bcrypt = require('bcryptjs');
return bcrypt.compare(candidatePassword, this.password);
};
// Static methods
userSchema.statics.findByEmail = function(email: string) {
return this.findOne({ email });
};
const User: Model<IUser> = mongoose.model('User', userSchema);
export default User;
CRUD Operations
Create
// Create single document
const user = new User({
name: 'John Doe',
email: 'john@example.com',
password: 'securepassword'
});
await user.save();
// Create with create()
const user2 = await User.create({
name: 'Jane Doe',
email: 'jane@example.com',
password: 'securepassword'
});
// Create multiple
const users = await User.insertMany([
{ name: 'User 1', email: 'user1@example.com', password: 'pass1' },
{ name: 'User 2', email: 'user2@example.com', password: 'pass2' }
]);
Read
// Find all
const allUsers = await User.find();
// Find with conditions
const activeAdmins = await User.find({
role: 'admin',
'profile.bio': { $exists: true }
});
// Find one
const user = await User.findOne({ email: 'john@example.com' });
// Find by ID
const userById = await User.findById('507f1f77bcf86cd799439011');
// Query operators
const users = await User.find({
age: { $gte: 18, $lte: 65 },
role: { $in: ['user', 'admin'] },
name: { $regex: /john/i }
});
// Projection (select fields)
const usersProjected = await User.find()
.select('name email -_id');
// Sort, limit, skip
const paginatedUsers = await User.find()
.sort({ createdAt: -1 })
.skip(10)
.limit(10);
// Populate references
const userWithPosts = await User.findById(userId)
.populate('posts', 'title content');
// Lean queries (plain objects, faster)
const leanUsers = await User.find().lean();
Update
// Update one
const result = await User.updateOne(
{ email: 'john@example.com' },
{ $set: { name: 'John Updated' } }
);
// Update many
await User.updateMany(
{ role: 'user' },
{ $set: { 'profile.verified': false } }
);
// Find and update (returns document)
const updatedUser = await User.findOneAndUpdate(
{ email: 'john@example.com' },
{ $set: { name: 'John Updated' } },
{ new: true, runValidators: true }
);
// Find by ID and update
const user = await User.findByIdAndUpdate(
userId,
{ $inc: { loginCount: 1 } },
{ new: true }
);
// Update operators
await User.updateOne({ _id: userId }, {
$set: { name: 'New Name' },
$unset: { temporaryField: '' },
$push: { tags: 'new-tag' },
$pull: { tags: 'old-tag' },
$addToSet: { tags: 'unique-tag' },
$inc: { viewCount: 1 }
});
Delete
// Delete one
await User.deleteOne({ email: 'john@example.com' });
// Delete many
await User.deleteMany({ role: 'guest' });
// Find and delete
const deletedUser = await User.findOneAndDelete({ email: 'john@example.com' });
// Find by ID and delete
const user = await User.findByIdAndDelete(userId);
Advanced Queries
Aggregation Pipeline
// Basic aggregation
const stats = await User.aggregate([
// Match stage
{ $match: { role: 'user' } },
// Group stage
{
$group: {
_id: '$role',
count: { $sum: 1 },
avgAge: { $avg: '$age' },
names: { $push: '$name' }
}
},
// Sort stage
{ $sort: { count: -1 } }
]);
// Complex aggregation
const postStats = await Post.aggregate([
// Lookup (join)
{
$lookup: {
from: 'users',
localField: 'author',
foreignField: '_id',
as: 'authorInfo'
}
},
{ $unwind: '$authorInfo' },
// Project
{
$project: {
title: 1,
authorName: '$authorInfo.name',
commentCount: { $size: '$comments' },
createdAt: 1
}
},
// Add fields
{
$addFields: {
isPopular: { $gte: ['$commentCount', 10] }
}
},
// Facet for multiple pipelines
{
$facet: {
popular: [
{ $match: { isPopular: true } },
{ $limit: 5 }
],
recent: [
{ $sort: { createdAt: -1 } },
{ $limit: 5 }
],
stats: [
{
$group: {
_id: null,
totalPosts: { $sum: 1 },
avgComments: { $avg: '$commentCount' }
}
}
]
}
}
]);
Text Search
// Create text index
userSchema.index({ name: 'text', 'profile.bio': 'text' });
// Search
const results = await User.find(
{ $text: { $search: 'developer javascript' } },
{ score: { $meta: 'textScore' } }
).sort({ score: { $meta: 'textScore' } });
Geospatial Queries
const locationSchema = new Schema({
name: String,
location: {
type: { type: String, enum: ['Point'], default: 'Point' },
coordinates: { type: [Number], required: true } // [longitude, latitude]
}
});
locationSchema.index({ location: '2dsphere' });
// Find nearby
const nearbyPlaces = await Location.find({
location: {
$near: {
$geometry: {
type: 'Point',
coordinates: [-73.9857, 40.7484] // NYC
},
$maxDistance: 5000 // meters
}
}
});
Relationships
Embedded Documents
// Good for: one-to-few, data always accessed together
const blogSchema = new Schema({
title: String,
content: String,
comments: [{
text: String,
author: String,
createdAt: { type: Date, default: Date.now }
}]
});
// Add comment
await Blog.findByIdAndUpdate(blogId, {
$push: { comments: { text: 'Great post!', author: 'John' } }
});
References
// Good for: one-to-many, data accessed separately
const postSchema = new Schema({
title: String,
author: { type: Schema.Types.ObjectId, ref: 'User' }
});
// Populate
const post = await Post.findById(postId).populate('author', 'name email');
// Deep populate
const post = await Post.findById(postId)
.populate({
path: 'author',
populate: { path: 'friends', select: 'name' }
});
Transactions
const session = await mongoose.startSession();
try {
session.startTransaction();
// Create user
const user = await User.create([{ name: 'John', email: 'john@example.com' }], { session });
// Create related post
await Post.create([{ title: 'First Post', author: user[0]._id }], { session });
// Update user
await User.updateOne(
{ _id: user[0]._id },
{ $inc: { postCount: 1 } },
{ session }
);
await session.commitTransaction();
console.log('Transaction committed');
} catch (error) {
await session.abortTransaction();
console.error('Transaction aborted:', error);
throw error;
} finally {
session.endSession();
}
Middleware (Hooks)
// Pre-save
userSchema.pre('save', function(next) {
this.updatedAt = new Date();
next();
});
// Post-save
userSchema.post('save', function(doc) {
console.log(`User ${doc.name} saved`);
});
// Pre-remove
userSchema.pre('remove', async function(next) {
// Delete user's posts
await Post.deleteMany({ author: this._id });
next();
});
// Query middleware
userSchema.pre('find', function() {
this.where({ isActive: true });
});
userSchema.pre('findOne', function() {
this.populate('posts');
});
Performance
Indexing
// Single field index
userSchema.index({ email: 1 });
// Compound index
userSchema.index({ name: 1, createdAt: -1 });
// Unique index
userSchema.index({ email: 1 }, { unique: true });
// Sparse index (only index documents with field)
userSchema.index({ phone: 1 }, { sparse: true });
// TTL index (auto-delete)
sessionSchema.index({ createdAt: 1 }, { expireAfterSeconds: 3600 });
// Explain query
const explanation = await User.find({ email: 'john@example.com' }).explain('executionStats');
Query Optimization
// Use projection
const users = await User.find().select('name email');
// Use lean() for read-only
const users = await User.find().lean();
// Batch operations
await User.bulkWrite([
{ insertOne: { document: { name: 'User 1' } } },
{ updateOne: { filter: { _id: id1 }, update: { $set: { active: true } } } },
{ deleteOne: { filter: { _id: id2 } } }
]);
// Cursor for large datasets
const cursor = User.find().cursor();
for await (const user of cursor) {
console.log(user.name);
}
Summary
| Operation | Method |
|---|---|
| Create | save(), create(), insertMany() |
| Read | find(), findOne(), findById() |
| Update | updateOne(), findByIdAndUpdate() |
| Delete | deleteOne(), findByIdAndDelete() |
| Aggregate | aggregate() |
| Populate | populate() |
MongoDB with Mongoose provides a powerful, flexible solution for Node.js applications requiring document-based data storage.
Advertisement
Moshiour Rahman
Software Architect & AI Engineer
Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.
Related Articles
GraphQL API Development: Complete Guide with Node.js
Master GraphQL API development from scratch. Learn schema design, resolvers, queries, mutations, subscriptions, and authentication best practices.
JavaScriptWebSockets: Build Real-Time Applications with Node.js
Master WebSocket development for real-time apps. Learn Socket.IO, authentication, scaling, error handling, and build chat and notification systems.
JavaScriptSupabase: Complete Backend-as-a-Service Guide
Master Supabase for full-stack development. Learn PostgreSQL database, authentication, real-time subscriptions, storage, and build modern applications.
Comments
Comments are powered by GitHub Discussions.
Configure Giscus at giscus.app to enable comments.