Mastering Backend Development with NodeJS: MongoDB Integration, Mongoose, CRUD Operations, and MVC Architecture

Sushant Gaurav - Aug 23 - - Dev Community

In our previous articles, we've laid the foundation of NodeJS, exploring its core concepts, including how NodeJS works, building servers, managing URLs, HTTP methods, utilizing the power of the Express framework, REST APIs, middleware, and HTTP headers. Now, we’ll take a step further by diving into databases particularly MongoDB, Models, Views, Controllers, and other essential components for building efficient and scalable backend applications.

Link to the previous articles:

  1. Getting Started with NodeJS
  2. Deepening NodeJS Knowledge: URLs, HTTP Methods, Express Framework, and Versioning
  3. Mastering NodeJS: REST APIs, Middleware, and HTTP Headers

Continuing our journey into NodeJS, this article explores how to integrate MongoDB with NodeJS, leverage Mongoose for seamless database interactions, and implement the MVC architecture in your projects.

Getting Started with MongoDB and NodeJS

What is MongoDB?

MongoDB is a NoSQL database that uses a flexible, document-oriented data model, allowing for easy storage of structured or semi-structured data.

  • Key Features:
    • Scalability: MongoDB is designed to scale out across distributed systems.
    • Flexibility: The document model allows for changes to data structure over time without needing to redefine schemas.
    • Performance: Optimized for high-volume data storage and retrieval.

Why Use MongoDB?

  • JSON-like Documents: MongoDB stores data in flexible, JSON-like documents, making it easy to work with and integrate with modern applications.
  • Schema-less: MongoDB doesn’t require a predefined schema, allowing for dynamic changes in data structures.
  • Scalability: Built to scale horizontally by distributing data across multiple servers.
  • Community Support: MongoDB has a large and active community, with comprehensive documentation and a wealth of resources.

Basic MongoDB Terminologies

  • Document: A record in MongoDB, similar to a row in relational databases, stored in BSON (Binary JSON) format.
  • Collection: A group of documents, similar to a table in relational databases.
  • Database: A container for collections.
  • Index: A data structure that improves the speed of data retrieval operations.

Example: MongoDB Document

Here’s an example of a MongoDB document representing a user:

{
  "_id": "507f1f77bcf86cd799439011",
  "name": "John Doe",
  "email": "johndoe@example.com",
  "age": 29,
  "created_at": "2023-08-21T15:00:00Z"
}
Enter fullscreen mode Exit fullscreen mode

Connecting NodeJS with MongoDB

How to Connect NodeJS to MongoDB?

To connect NodeJS with MongoDB, we’ll use the MongoDB Node.js driver.

Installation:

npm install mongodb
Enter fullscreen mode Exit fullscreen mode

Code:

const { MongoClient } = require('mongodb');
const url = 'mongodb://localhost:27017';
const client = new MongoClient(url);

async function connectDB() {
    try {
        await client.connect();
        console.log('Connected to MongoDB');
        const db = client.db('testdb');
        const collection = db.collection('users');
        // Perform database operations
    } catch (error) {
        console.error('Error connecting to MongoDB', error);
    }
}

connectDB();
Enter fullscreen mode Exit fullscreen mode

What is Mongoose?

Mongoose is an Object Data Modeling (ODM) library for MongoDB and NodeJS. It provides a straightforward, schema-based solution to model application data.

Mongoose simplifies interactions with MongoDB by providing schema validation, middleware, and a convenient API for database operations.

Installation:

npm install mongoose
Enter fullscreen mode Exit fullscreen mode

Code:

const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/testdb', {
    useNewUrlParser: true,
    useUnifiedTopology: true
}).then(() => {
    console.log('Connected to MongoDB using Mongoose');
}).catch(err => {
    console.error('Error connecting to MongoDB', err);
});
Enter fullscreen mode Exit fullscreen mode

Basic CRUD Operations with MongoDB and Mongoose

Creating a Schema and Model

Code:

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
    name: String,
    email: String,
    age: Number,
    created_at: {
        type: Date,
        default: Date.now
    }
});

const User = mongoose.model('User', userSchema);
Enter fullscreen mode Exit fullscreen mode

Create Operation (Insert Data)

Code:

async function createUser() {
    const newUser = new User({
        name: 'John Doe',
        email: 'johndoe@example.com',
        age: 29
    });

    const result = await newUser.save();
    console.log('User Created:', result);
}

createUser();
Enter fullscreen mode Exit fullscreen mode

Output:

User Created: { _id: 507f1f77bcf86cd799439011, name: 'John Doe', email: 'johndoe@example.com', age: 29, created_at: 2023-08-21T15:00:00Z }
Enter fullscreen mode Exit fullscreen mode

Read Operation (Retrieve Data)

Code:

async function getUsers() {
    const users = await User.find();
    console.log('Users:', users);
}

getUsers();
Enter fullscreen mode Exit fullscreen mode

Output:

Users: [ { _id: 507f1f77bcf86cd799439011, name: 'John Doe', email: 'johndoe@example.com', age: 29, created_at: 2023-08-21T15:00:00Z } ]
Enter fullscreen mode Exit fullscreen mode

Update Operation

Code:

async function updateUser(id) {
    const user = await User.findByIdAndUpdate(id, { name: 'Jane Doe' }, { new: true });
    console.log('User Updated:', user);
}

updateUser('507f1f77bcf86cd799439011');
Enter fullscreen mode Exit fullscreen mode

Output:

User Updated: { _id: 507f1f77bcf86cd799439011, name: 'Jane Doe', email: 'johndoe@example.com', age: 29, created_at: 2023-08-21T15:00:00Z }
Enter fullscreen mode Exit fullscreen mode

Delete Operation

Code:

async function deleteUser(id) {
    const result = await User.findByIdAndDelete(id);
    console.log('User Deleted:', result);
}

deleteUser('507f1f77bcf86cd799439011');
Enter fullscreen mode Exit fullscreen mode

Output:

User Deleted: { _id: 507f1f77bcf86cd799439011, name: 'Jane Doe', email: 'johndoe@example.com', age: 29, created_at: 2023-08-21T15:00:00Z }
Enter fullscreen mode Exit fullscreen mode

Timestamps in Mongoose

Timestamps are fields that store the creation and update times of a document.

Timestamps help track when a document was created and last updated, which is essential for auditing and maintaining data integrity.

Code:

const userSchema = new mongoose.Schema({
    name: String,
    email: String,
    age: Number
}, { timestamps: true });
Enter fullscreen mode Exit fullscreen mode

Result: Automatically adds createdAt and updatedAt fields to the schema.

Understanding MVC Architecture

What is MVC?

MVC (Model-View-Controller) is a design pattern that separates an application into three interconnected components.

  • Model: Represents the data and business logic.
  • View: The user interface that displays data.
  • Controller: Handles user input and interacts with the model to update the view.
    • Purpose: MVC promotes separation of concerns, making the application easier to manage, test, and scale.

Why Use MVC?

  • Organized Codebase: MVC organizes your code into separate components, making it easier to maintain and scale.
  • Reusability: Components in MVC can be reused across different parts of the application.
  • Testability: Each component can be tested independently, improving the reliability of your application.

When to Use MVC?

  • Large Applications: MVC is ideal for large-scale applications with complex business logic and a need for organized code.
  • Scalability: When planning for future growth, MVC allows for easy expansion of the application.

Use Case: Project with and without MVC

Without using MVC

Let us take an example of a simple NodeJS application where all logic (database interaction, business logic, and routing) is handled in a single file.

  • Code Example:
const express = require('express');
const mongoose = require('mongoose');

const app = express();
mongoose.connect('mongodb://localhost:27017/simpledb');

app.use(express.json());

app.post('/users', (req, res) => {
    // Directly handle database interaction and response
    const newUser = new User(req.body);
    newUser.save().then(user => res.send(user));
});

app.listen(3000, () => console.log('Server running'));
Enter fullscreen mode Exit fullscreen mode
  • Issues:
    • Tightly Coupled Code: The application logic is tightly coupled, making it difficult to maintain or scale.
    • Harder to Test: Testing individual components or logic is more challenging.

With MVC

Let us take a NodeJS application with a clear separation of concerns, following the MVC pattern.

  • Code Example:
    • Model:
  const mongoose = require('mongoose');
  const userSchema = new mongoose.Schema({
      name: String,
      email: String,
      age: Number
  });
  const User = mongoose.model('User', userSchema);
Enter fullscreen mode Exit fullscreen mode
  • Controller:
  const User = require('../models/user');

  exports.createUser = async (req, res) => {
      const newUser = new User(req.body);
      const user = await newUser.save();
      res.send(user);
  };
Enter fullscreen mode Exit fullscreen mode
  • Route:
  const express = require('express');
  const userController = require('../controllers/userController');

  const router = express.Router();

  router.post('/users', userController.createUser);

  module.exports = router;
Enter fullscreen mode Exit fullscreen mode
  • Main App:
  const express = require('express');
  const mongoose = require('mongoose');
  const userRoutes = require('./routes/userRoutes');

  const app = express();
  mongoose.connect('mongodb://localhost:27017/mvcdb');

  app.use(express.json());
  app.use('/api', userRoutes);

  app.listen(3000, () => console.log('Server running'));
Enter fullscreen mode Exit fullscreen mode
  • Benefits:
    • Separation of Concerns: Each part of the application has a clear responsibility, making the code easier to manage and scale.
    • Testability: Individual components (like models and controllers) can be tested independently.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .