MongoDB with Node.js Complete Guide

Database Expert 15 min read Database 980 views
MongoDB with Node.js Tutorial

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

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.