Skip to content
FullStackDostFullStackDostLearn · Build · Level Up
  • All Courses
  • Updates
  • My Account
  • Practice
  • All Courses
  • Updates
  • My Account
  • Practice
  • Home
  • Web development

MongoDB (NoSQL)

Curriculum

  • 10 Sections
  • 31 Lessons
  • 10 Weeks
Expand all sectionsCollapse all sections
  • Introduction to MongoDB
    MongoDB is a NoSQL database that is designed for handling large volumes of unstructured or semi-structured data. Unlike traditional relational databases (RDBMS) that use tables and rows to organize data, MongoDB stores data in a flexible document-oriented format using JSON-like documents (BSON - Binary JSON). This makes it highly scalable, flexible, and performant for applications that need to handle varying types of data with complex structures.
    5
    • 1.1
      What is MongoDB?
    • 1.2
      Why MongoDB?
    • 1.3
      When to use MongoDB?
    • 1.4
      Key Features of MongoDB
    • 1.5
      Installing MongoDB
  • MongoDB Basic Operations
    MongoDB provides a rich set of basic operations for interacting with the database, including creating, reading, updating, and deleting data (often abbreviated as CRUD operations). Below are the basic operations that you can perform with MongoDB.
    2
    • 2.1
      Database and Collection Basics: Your First Steps in MongoDB
    • 2.2
      MongoDB CRUD Operations: Create, Read, Update, Delete
  • Advanced Querying Techniques
    MongoDB offers a rich set of querying capabilities, and as you work with larger datasets and more complex application requirements, you’ll often need to use advanced querying techniques. These techniques help you optimize performance, execute sophisticated queries, and leverage MongoDB’s powerful indexing and aggregation features.
    4
    • 3.1
      Query Filters and Operators
    • 3.2
      Advanced MongoDB Querying: Unlocking Powerful Data Retrieval
    • 3.3
      Sorting and Limiting Results
    • 3.4
      Aggregation Framework: Transform and Analyze Data in MongoDB
  • Data Modeling and Schema Design
    Data modeling and schema design are critical when using MongoDB (or any NoSQL database) to ensure efficient data storage, fast queries, and scalability. Unlike relational databases, MongoDB is schema-less, which means you are not required to define a fixed schema upfront. However, making the right design decisions from the beginning is essential for maintaining performance and avoid complications as your data grows.
    4
    • 4.1
      MongoDB Data Modeling: Designing for Performance and Scale
    • 4.2
      Document Structure
    • 4.3
      Schema Design Patterns
    • 4.4
      MongoDB and Relationships
  • Indexing and Performance Optimization
    In MongoDB, indexing is a critical part of performance optimization. Without proper indexes, MongoDB has to scan every document in a collection to satisfy queries, which can be very inefficient for large datasets. Indexes are used to quickly locate data without scanning every document, making reads faster and more efficient.
    3
    • 5.1
      Creating Indexes in MongoDB: Supercharge Your Queries
    • 5.2
      Using Text Search
    • 5.3
      Performance Optimization
  • Integrating MongoDB with a Web Application (Node.js)
    Integrating MongoDB with a web application built using Node.js is a common and powerful combination for building scalable and efficient web apps. MongoDB’s flexibility with JSON-like data and Node.js's asynchronous event-driven architecture work well together. In this guide, I'll walk you through the steps for integrating MongoDB with a Node.js web application, covering the essentials of setting up the connection, performing CRUD operations, and using popular libraries.
    3
    • 6.1
      Setting Up MongoDB with Node.js
    • 6.2
      Mastering CRUD Operations with Mongoose in Node.js
    • 6.3
      Error Handling and Validation
  • Security in MongoDB
    Security is an essential aspect when working with MongoDB, especially when handling sensitive data in production environments. MongoDB provides a variety of security features to help protect your data against unauthorized access, injection attacks, and other vulnerabilities. Here’s a guide on securing MongoDB and your Node.js application when interacting with MongoDB.
    2
    • 7.1
      MongoDB Security: Authentication and Authorization Essentials
    • 7.2
      Data Encryption in MongoDB: Securing Your NoSQL Data
  • Working with MongoDB in Production
    3
    • 8.1
      MongoDB Backup and Restore
    • 8.2
      MongoDB Scaling and Sharding
    • 8.3
      MongoDB Replication
  • Deploying and Monitoring MongoDB
    Working with MongoDB in a production environment requires careful planning, attention to detail, and best practices to ensure optimal performance, security, reliability, and scalability.
    3
    • 9.1
      Deploying MongoDB to Production: A Comprehensive Guide
    • 9.2
      Monitoring and Management
    • 9.3
      Summary for MongoDB deployment on Production
  • Building a Web App with MongoDB (Final Project)
    Demo Project (OneStopShop)
    2
    • 10.1
      Building the Application
    • 10.2
      Final Project Features

Mastering CRUD Operations with Mongoose in Node.js

Introduction to Mongoose CRUD Operations

Namaste, developers! Welcome to this essential lesson where we’ll demystify the core of almost every web application: CRUD operations. CRUD stands for Create, Read, Update, and Delete – the fundamental actions you perform on data. In the world of Node.js and MongoDB, Mongoose acts as our powerful Object Data Modeling (ODM) library, making these operations intuitive and efficient. Think of Mongoose as a translator that helps your Node.js application speak fluent MongoDB, using structured schemas.

By the end of this lesson, you’ll have a fully functional RESTful API built with Express.js and Mongoose, capable of managing data in your MongoDB database. Let’s dive in!

What is an ODM? An Object Data Modeling (ODM) library, like Mongoose for MongoDB, helps you work with database data as JavaScript objects. It provides schema definitions, data validation, and powerful query tools, abstracting away raw database commands.

1. Setting Up Your Project: Dependencies and Connection

Before we write our CRUD logic, we need to set up our Node.js project, install necessary packages, and establish a connection to our MongoDB database. Ensure you have Node.js and MongoDB installed and running on your system.

Step 1.1: Initialize Project and Install Dependencies

First, create a new directory for your project, navigate into it, and initialize a new Node.js project. Then, install Express (for building our API) and Mongoose (for MongoDB interaction).

mkdir mongoose-crud-api
cd mongoose-crud-api
npm init -y
npm install express mongoose dotenv

We’ve also added dotenv to manage environment variables securely, especially for our MongoDB connection string.

Step 1.2: Configure MongoDB Connection

Create a .env file in your project root to store your MongoDB connection URI. This keeps sensitive information out of your codebase.

# .env
MONGODB_URI=mongodb://localhost:27017/mydatabase # For local MongoDB
# MONGODB_URI=mongodb+srv://<username>:<password>@cluster0.abcde.mongodb.net/mydatabase?retryWrites=true&w=majority # For MongoDB Atlas
PORT=3000

Now, let’s create our main server file, server.js, and establish the connection.

// server.js
require('dotenv').config(); // Load environment variables
const express = require('express');
const mongoose = require('mongoose');

const app = express();

// Middleware to parse JSON request bodies
app.use(express.json());

// Connect to MongoDB
mongoose.connect(process.env.MONGODB_URI)
  .then(() => console.log('MongoDB Connected Successfully!'))
  .catch(err => console.error('MongoDB connection error:', err));

// Basic route to check server status
app.get('/', (req, res) => {
  res.send('Welcome to the Mongoose CRUD API!');
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}...`);
});

Tip: Always use dotenv or similar libraries for sensitive credentials like database URIs. Never hardcode them directly into your application files, especially for production environments!

2. Defining Our Mongoose Model (Schema)

A Mongoose model is a blueprint for documents in your MongoDB collection. It enforces structure and validation. Let’s create a User model.

Step 2.1: Create models/User.js

Create a new folder named models and inside it, a file called User.js. This file will define our user schema and export the Mongoose model.

// models/User.js
const mongoose = require('mongoose');

// Define the schema for the User collection
const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: [true, 'User name is required'], // Custom error message
    trim: true // Removes whitespace from both ends of a string
  },
  email: {
    type: String,
    required: [true, 'Email is required'],
    unique: true, // Ensures email addresses are unique across the collection
    lowercase: true, // Converts email to lowercase before saving
    match: [/^S+@S+.S+$/, 'Please use a valid email address'] // Regex for email validation
  },
  age: {
    type: Number,
    required: [true, 'Age is required'],
    min: [18, 'User must be at least 18 years old'] // Minimum age validation
  },
  createdAt: {
    type: Date,
    default: Date.now // Automatically sets the creation date
  }
});

// Create a model from the schema
const User = mongoose.model('User', userSchema);

module.exports = User;

In this schema, we’ve added robust validation rules:

  • required: Ensures the field must exist.
  • unique: Guarantees no two documents will have the same value for this field.
  • trim, lowercase: Useful data sanitization.
  • match, min: Specific validation rules with custom error messages.
  • default: Sets a default value if not provided.

3. Implementing CRUD Operations (Routes)

Now, let’s integrate our User model into server.js and define the API endpoints for our CRUD operations. We’ll use Express routes to handle incoming HTTP requests.

Step 3.1: Import Model and Add Routes to server.js

Add the following routes to your server.js file, ideally after your MongoDB connection and before `app.listen`.

Create (POST: /users)

To create a new user, clients will send a POST request to /users with the user data in the request body. Mongoose’s .save() method handles inserting the document.

// Create a new user
app.post('/users', async (req, res) => {
  try {
    const newUser = new User(req.body); // Create a new User instance with data from request body
    await newUser.save(); // Save the new user to the database
    res.status(201).json(newUser); // Respond with the created user and 201 Created status
  } catch (err) {
    // Handle Mongoose validation errors specifically
    if (err.name === 'ValidationError') {
      const errors = Object.values(err.errors).map(el => el.message);
      return res.status(400).json({ message: errors.join(', ') });
    }
    // Handle duplicate key errors (e.g., unique email constraint)
    if (err.code === 11000) {
      return res.status(409).json({ message: 'Email already exists.' }); // 409 Conflict
    }
    res.status(500).json({ message: 'Error creating user', error: err.message });
  }
});

HTTP Status Codes:

  • 201 Created: Indicates a new resource has been successfully created.
  • 400 Bad Request: The server cannot process the request due to client error (e.g., invalid input).
  • 409 Conflict: The request could not be completed due to a conflict with the current state of the target resource (e.g., duplicate unique key).
  • 500 Internal Server Error: A generic error message when an unexpected condition was encountered.

Read (GET: /users and /users/:id)

We’ll implement two read operations:

  • Get all users: GET /users using User.find().
  • Get a single user by ID: GET /users/:id using User.findById().
// Get all users
app.get('/users', async (req, res) => {
  try {
    const users = await User.find(); // Retrieve all users from the database
    res.status(200).json(users); // Respond with the array of users
  } catch (err) {
    res.status(500).json({ message: 'Error fetching users', error: err.message });
  }
});

// Get a single user by ID
app.get('/users/:id', async (req, res) => {
  try {
    const user = await User.findById(req.params.id); // Find a user by their unique ID
    if (!user) {
      return res.status(404).json({ message: 'User not found' }); // 404 Not Found
    }
    res.status(200).json(user); // Respond with the found user
  } catch (err) {
    // Handle invalid ID format (e.g., not a valid MongoDB ObjectId)
    if (err.name === 'CastError') {
      return res.status(400).json({ message: 'Invalid User ID format' });
    }
    res.status(500).json({ message: 'Error fetching user', error: err.message });
  }
});

Update (PUT: /users/:id)

To update an existing user, clients will send a PUT request to /users/:id with the updated data. Mongoose’s findByIdAndUpdate() method is perfect for this.

// Update user by ID
app.put('/users/:id', async (req, res) => {
  try {
    // findByIdAndUpdate(id, update, options)
    const updatedUser = await User.findByIdAndUpdate(
      req.params.id,
      req.body,
      { new: true, runValidators: true } // Options: return the new doc, run schema validators
    );

    if (!updatedUser) {
      return res.status(404).json({ message: 'User not found' });
    }
    res.status(200).json(updatedUser); // Respond with the updated user
  } catch (err) {
    if (err.name === 'ValidationError') {
      const errors = Object.values(err.errors).map(el => el.message);
      return res.status(400).json({ message: errors.join(', ') });
    }
    if (err.code === 11000) {
      return res.status(409).json({ message: 'Email already exists.' });
    }
    if (err.name === 'CastError') {
      return res.status(400).json({ message: 'Invalid User ID format' });
    }
    res.status(500).json({ message: 'Error updating user', error: err.message });
  }
});

Important findByIdAndUpdate Options:

  • { new: true }: By default, findByIdAndUpdate returns the original document before the update was applied. Setting new: true ensures it returns the updated document.
  • { runValidators: true }: By default, Mongoose schema validators do not run on findByIdAndUpdate operations. This option forces them to run, ensuring your updated data adheres to your schema rules.

Delete (DELETE: /users/:id)

To remove a user, clients send a DELETE request to /users/:id. Mongoose’s findByIdAndDelete() method will remove the document.

// Delete user by ID
app.delete('/users/:id', async (req, res) => {
  try {
    const deletedUser = await User.findByIdAndDelete(req.params.id); // Find and delete a user by ID

    if (!deletedUser) {
      return res.status(404).json({ message: 'User not found' });
    }
    res.status(200).json({ message: 'User deleted successfully' }); // Respond with success message
  } catch (err) {
    if (err.name === 'CastError') {
      return res.status(400).json({ message: 'Invalid User ID format' });
    }
    res.status(500).json({ message: 'Error deleting user', error: err.message });
  }
});

4. Testing Your CRUD API

With all routes defined, it’s time to test! Start your server:

node server.js

You can use tools like Postman, Insomnia, or even curl from your terminal to send requests.

Example curl Commands:

1. Create a User (POST)

curl -X POST -H "Content-Type: application/json" 
     -d '{"name": "Alice Smith", "email": "alice.smith@example.com", "age": 28}' 
     http://localhost:3000/users

2. Get All Users (GET)

curl http://localhost:3000/users

3. Get User by ID (GET) (Replace <USER_ID> with an actual ID from your database)

curl http://localhost:3000/users/<USER_ID>

4. Update User (PUT) (Replace <USER_ID> with an actual ID)

curl -X PUT -H "Content-Type: application/json" 
     -d '{"name": "Alice Wonderland", "email": "alice.w@example.com", "age": 29}' 
     http://localhost:3000/users/<USER_ID>

5. Delete User (DELETE) (Replace <USER_ID> with an actual ID)

curl -X DELETE http://localhost:3000/users/<USER_ID>

Practice Exercise: Enhancing Your User API

It’s your turn to apply what you’ve learned! Extend the current User API with the following enhancements:

  1. Add a new field to the User Schema: isActive

    Modify models/User.js to include an isActive field of type Boolean, with a default value of true. This could indicate whether an account is active or suspended.

  2. Implement a “Soft Delete” Route

    Instead of permanently deleting a user, often in real-world applications, we perform a “soft delete” by marking them as inactive. Create a new PATCH route (e.g., /users/:id/deactivate) that finds a user by ID and updates their isActive field to false. Ensure you return the updated user.

  3. Create a Read Route for Active Users Only

    Add a new GET route (e.g., /users/active) that retrieves only users where isActive is true. This will require using a Mongoose query condition.

  4. Add Pagination to Get All Users

    Modify the GET /users route to support basic pagination. It should accept optional query parameters page (default 1) and limit (default 10). Use Mongoose’s .skip() and .limit() methods to implement this. For example: /users?page=2&limit=5.

Summary

Congratulations! You’ve successfully built a robust RESTful API with Node.js, Express, and Mongoose, mastering the essential CRUD operations. You learned how to define structured schemas, connect to MongoDB, handle various HTTP requests, and implement proper error handling and validation. This foundation is crucial for building any data-driven application.

Keep experimenting with Mongoose’s powerful querying capabilities and explore more advanced features like middleware and population. Happy coding!

Setting Up MongoDB with Node.js
Prev
Error Handling and Validation
Next

Copyright © 2026 FullStackDost. All Rights Reserved.

  • Privacy Policy
  • Terms of Service
  • Contact Support

Powered by EduPress