How to Create a RESTful API with Node.js, Express, and MongoDB using MVC pattern

Muhammad InamMuhammad Inam
5 min read

In this post, we'll explore setting up a basic RESTful API using Node.js, Express.js, MongoDB, and Mongoose using the MVC (Model-View-Controller) pattern. By structuring our application this way, we create a modular, organized, and scalable project.

What is the MVC Pattern?

MVC is an architectural pattern that divides an application into three main interconnected components:

  1. Model: Represents the data and business logic.

  2. View: Handles the user interface and presentation.

  3. Controller: Acts as an intermediary between the Model and View, processing user inputs and updating the Model or View as needed.

This separation helps in managing complex applications, as each component has a distinct role, making the codebase more organized and easier to maintain.

Why Use MVC?

MVC provides several benefits:

  • Separation of Concerns: Each component has a well-defined role, making it easier to manage and modify.

  • Scalability: The structure is modular, allowing teams to work on different parts of the application concurrently.

  • Reusability: The View can be reused for different parts of the application, while the Model can serve different views, making the architecture flexible.

  • Testability: Each component can be tested independently, leading to cleaner and more reliable code.

Project Overview

Our tech stack for this API includes:

  • Node.js: JavaScript runtime for backend development

  • Express.js: Web application framework

  • MongoDB: NoSQL database

  • Mongoose: ODM for MongoDB

  • Nodemon: Automatically restarts the server on file changes

In this setup:

  • Models represent our data and business logic.

  • Controllers handle the main application logic.

  • Routes connect HTTP requests to the controller functions.

1. Project Setup

Start by creating a new project folder and installing the necessary packages.

mkdir MVCPattern
cd MVCPattern
npm init -y
npm install express mongoose dotenv nodemon

2. Environment Variables

Add a .env file in the root directory to store sensitive information:

PORT=3000
MONGODB_URI=<Your MongoDB URI>

3. Setting Up the MVC Structure

Your project structure should look like this:

MVCPattern/
│
├── config/
│   └── db.js        # MongoDB connection
├── controllers/
│   └── productController.js # Business logic for products
├── models/
│   └── productModel.js  # Mongoose schema
├── routes/
│   └── productsRoute.js  # Route handlers for product endpoints
├── .env               # Environment variables
└── index.js           # Main server file

4. Configuring MongoDB Connection

In config/db.js, configure Mongoose to connect to MongoDB using the URI from our .env file.

// config/db.js
const mongoose = require("mongoose");
const dotenv = require("dotenv");

dotenv.config();

const connectDB = async () => {
  try {
    const conn = await mongoose.connect(process.env.MONGODB_URI, {
      useNewUrlParser: true,
    });
    console.log(`MongoDB Connected: ${conn.connection.host}`);
  } catch (error) {
    console.error(error.message);
    process.exit(1);
  }
};

module.exports = connectDB;

5. Server Setup in index.js

Our main server file, index.js, sets up Express, connects to MongoDB, and loads the routes.

// index.js
const express = require("express");
const connectDB = require("./config/db");
const dotenv = require("dotenv");
const productsRoute = require("./routes/productsRoute");
const app = express();

dotenv.config();
const port = process.env.PORT;

// Connect to MongoDB
connectDB();

// Middleware
app.use(express.json());

// Routes
app.get("/", (req, res) => {
  res.send("Backend API");
});
app.use("/api", productsRoute);

// Start server
app.listen(port, () => {
  console.log(`Server is running on port: ${port}`);
});

6. Defining the Product Model

The Product Model defines the data structure for products and is stored in models/productModel.js. This schema is managed by Mongoose, allowing us to interact with MongoDB collections as JavaScript objects.

// models/productModel.js
const { Schema, model } = require("mongoose");

const ProductSchema = new Schema({
  name: {
    type: String,
    required: true,
  },
  price: {
    type: String,
    required: true,
  },
  description: {
    type: String,
    required: true,
  },
  category: {
    type: String,
    required: true,
  },
  createdAt: {
    type: Date,
    default: Date.now,
  },
});

const ProductModel = model("Products", ProductSchema);

module.exports = ProductModel;

7. Creating the Controller for Business Logic

The Controller in controllers/productController.js contains all the business logic for handling CRUD operations. Each function interacts with the model to perform database operations.

// controllers/productController.js
const Product = require("../models/productModel");

const getAllProducts = async (req, res) => {
  try {
    const allProducts = await Product.find();
    res.status(200).json({
      success: true,
      products: allProducts,
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      message: "Internal Server Error...",
    });
  }
};

const createProduct = async (req, res) => {
  try {
    const { name, price, description, category } = req.body;
    const newProduct = new Product({ name, price, description, category });
    await newProduct.save();
    res.status(201).json({
      success: true,
      product: newProduct,
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      message: "Internal Server Error...",
    });
  }
};

const updateProduct = async (req, res) => {
  try {
    const { id } = req.params;
    const { name, price, description, category } = req.body;
    const updatedProduct = await Product.findByIdAndUpdate(
      id,
      { name, price, description, category },
      { new: true }
    );
    res.status(200).json({
      success: true,
      product: updatedProduct,
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      message: "Internal Server Error...",
    });
  }
};

const deleteProduct = async (req, res) => {
  try {
    const { id } = req.params;
    const deletedProduct = await Product.findByIdAndDelete(id);
    if (!deletedProduct) {
      return res.status(404).json({ message: "Product not found." });
    }
    res.status(200).json({
      success: true,
      message: "Product deleted successfully.",
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      message: "Internal Server Error...",
    });
  }
};

module.exports = {
  getAllProducts,
  createProduct,
  updateProduct,
  deleteProduct,
};

8. Setting Up Routes

The Router in routes/productsRoute.js connects each endpoint to its corresponding controller function.

// routes/productsRoute.js
const express = require("express");
const router = express.Router();
const {
  getAllProducts,
  updateProduct,
  createProduct,
  deleteProduct,
} = require("../controllers/productController");

router.get("/products", getAllProducts);
router.post("/products", createProduct);
router.put("/products/:id", updateProduct);
router.delete("/products/:id", deleteProduct);

module.exports = router;

Testing the API

With this setup, you can test each endpoint using a tool like Postman:

  1. GET /api/products - Fetch all products

  2. POST /api/products - Create a new product

  3. PUT /api/products/:id - Update an existing product by ID

  4. DELETE /api/products/:id - Delete a product by ID

Conclusion

In this tutorial, we structured a Node.js application using the MVC pattern with Express, MongoDB, and Mongoose. This pattern organizes code into modular sections, keeping our project maintainable and scalable.

0
Subscribe to my newsletter

Read articles from Muhammad Inam directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Muhammad Inam
Muhammad Inam

"Frontend developer skilled in React and passionate about building seamless, user-friendly web applications. I’m currently expanding my expertise in Backend development with Node.js and Express.js and exploring full-stack projects with Supabase and Firebase. Enthusiastic about creating meaningful content and sharing insights through blog posts, I enjoy contributing to the tech community and collaborating on innovative projects. Open to freelance opportunities, open-source contributions, and connecting with fellow developers!"