Skip to main content
MongoDB
CHAPTER 25 Beginner

req.body.title`), or use `$jsonSchema` database validation (Chapter 18).

Updated: May 16, 2026
15 min read

# CHAPTER 25

Building CRUD Applications with MongoDB

1. Introduction

Nearly every web application on earth—from a simple Todo list to massive platforms like Twitter or Airbnb—is essentially a CRUD application. CRUD is an acronym for the four fundamental database operations: Create, Read, Update, and Delete. In this chapter, we will synthesize everything we have learned by designing the complete Node.js/Express backend architecture for a fully functional Task Management System (like Trello).

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Map HTTP REST methods to MongoDB operations.
  • Implement the "Create" operation using POST and insertOne().
  • Implement the "Read" operation using GET and find().
  • Implement the "Update" operation using PUT and updateOne().
  • Implement the "Delete" operation using DELETE and deleteOne().
  • Architect a clean, secure API workflow.

3. The Architecture Map (REST API)

When building a modern web app, data flows from the User's Browser (React/Vue) -> to the Express.js Server (Backend) -> to MongoDB (Database). We use RESTful API endpoints.
  • Create: Browser sends POST /tasks -> Express runs insertOne().
  • Read: Browser sends GET /tasks -> Express runs find().
  • Update: Browser sends PUT /tasks/:id -> Express runs updateOne().
  • Delete: Browser sends DELETE /tasks/:id -> Express runs deleteOne().

4. Step 1: The Express Setup

First, we establish our server and connect to MongoDB (as learned in Chapter 24).
javascript
123456789101112
const express = require('express');
const { MongoClient, ObjectId } = require('mongodb');

const app = express();
app.use(express.json()); // Allows Express to read JSON body payloads

let db;
MongoClient.connect("mongodb://localhost:27017")
    .then(client => {
        db = client.db("task_manager");
        app.listen(3000, () => console.log("Server running on port 3000"));
    });

5. Step 2: C - CREATE (Adding a Task)

When a user submits a new task, the frontend sends a POST request. The data arrives in req.body. We inject it into MongoDB.
javascript
1234567891011121314
app.post('/tasks', async (req, res) => {
    try {
        const newTask = {
            title: req.body.title,
            status: "Pending", // Default value
            created_at: new Date()
        };
        
        const result = await db.collection("tasks").insertOne(newTask);
        res.status(201).json({ message: "Task created!", id: result.insertedId });
    } catch (error) {
        res.status(500).json({ error: "Could not create task." });
    }
});

6. Step 3: R - READ (Fetching Tasks)

When the user opens the app, we need to fetch all their tasks and send them to the browser.
javascript
12345678910111213
app.get('/tasks', async (req, res) => {
    try {
        // Fetch all tasks, sort newest first, and convert cursor to Array!
        const tasks = await db.collection("tasks")
                              .find()
                              .sort({ created_at: -1 })
                              .toArray();
                              
        res.status(200).json(tasks);
    } catch (error) {
        res.status(500).json({ error: "Could not fetch tasks." });
    }
});

7. Step 4: U - UPDATE (Editing a Task)

If a user drags a task into the "Completed" column, the frontend sends a PUT request with the Task ID in the URL. We MUST convert the ID string to an ObjectId and use the $set operator!
javascript
123456789101112131415
app.put('/tasks/:id', async (req, res) => {
    try {
        const targetId = new ObjectId(req.params.id);
        const updatedStatus = req.body.status;
        
        const result = await db.collection("tasks").updateOne(
            { _id: targetId },
            { $set: { status: updatedStatus } }
        );
        
        res.status(200).json({ message: "Task updated successfully!" });
    } catch (error) {
        res.status(500).json({ error: "Could not update task." });
    }
});

8. Step 5: D - DELETE (Removing a Task)

When the user clicks the Trash icon, the frontend sends a DELETE request.
javascript
123456789101112131415
app.delete('/tasks/:id', async (req, res) => {
    try {
        const targetId = new ObjectId(req.params.id);
        
        const result = await db.collection("tasks").deleteOne({ _id: targetId });
        
        if (result.deletedCount === 1) {
            res.status(200).json({ message: "Task permanently deleted." });
        } else {
            res.status(404).json({ error: "Task not found." });
        }
    } catch (error) {
        res.status(500).json({ error: "Could not delete task." });
    }
});

9. Common Mistakes

  • Dumping req.body directly into MongoDB: It is highly dangerous to write insertOne(req.body). If a hacker sends {"title": "Hack", "isadmin": true} in the body, your database blindly saves it. Always manually destruct and validate the fields you expect (e.g., title: req.body.title), or use $jsonSchema database validation (Chapter 18).
  • Crashing on Invalid ObjectIds: If a user navigates to /tasks/123, new ObjectId('123') will throw a fatal error and crash the Node server because '123' is not a valid 12-byte hex string. You must wrap ObjectId conversions in a try/catch block.

10. Best Practices

  • Soft Deletes: In enterprise applications, we rarely use physical DELETE requests. Instead, we use a PUT request to update a boolean column: { $set: { isdeleted: true } }. The "Read" script is modified to find({ is_deleted: false }). The data appears deleted to the user, but is preserved for auditing!

11. Exercises

  1. 1. Which HTTP method and MongoDB method combination is used for the "Create" operation?
  1. 2. Why must req.params.id be converted before it can be used in a updateOne() filter?

12. MongoDB Challenges

Write the core Express route logic for a GET /tasks/:id endpoint. It should extract the ID, convert it, and use findOne() to return a single specific task.
javascript
1234
app.get('/tasks/:id', async (req, res) => {
    const task = await db.collection("tasks").findOne({ _id: new ObjectId(req.params.id) });
    res.json(task);
});

13. MCQ Quiz with Answers

Question 1

In the acronym CRUD, what core MongoDB methods correspond to the four operations?

Question 2

When a React frontend sends an ID string (e.g., "650a2b...") to an Express backend to delete a document, what must the backend developer do before passing that ID to the deleteOne() method?

14. Interview Questions

  • Q: Explain the concept of a "Soft Delete" versus a "Hard Delete" in database architecture. Why do enterprise systems heavily favor soft deletes?
  • Q: You are reviewing a junior developer's code. They wrote db.collection('users').insertOne(req.body). Explain the catastrophic security vulnerability (NoSQL Injection/Mass Assignment) present in this code, and how to fix it.

15. FAQs

Q: Do I really have to write all this boilerplate for every collection? A: If you are using the Native Driver, yes. This is exactly why the industry relies on Mongoose ORM. Mongoose abstracts all this boilerplate away, allowing you to write Task.findByIdAndUpdate(id) and it automatically handles the ObjectId conversion and validation!

16. Summary

You have mastered the complete lifecycle of web application data. By expertly mapping HTTP REST endpoints to their corresponding insertOne, find, updateOne, and deleteOne commands, you can architect the MERN stack backend foundation for virtually any software platform in existence.

17. Next Chapter Recommendation

Our CRUD application works perfectly. But what happens 3 years from now when the Task database has 10 million documents, and the "Read" operation starts taking 8 seconds to load? In Chapter 26: MongoDB Performance Optimization, we will return to the database internals to master advanced performance scaling.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·