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:
- Model: Represents the data and business logic.
- View: Handles the user interface and presentation.
- 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:
- GET /api/products - Fetch all products
- POST /api/products - Create a new product
- PUT /api/products/:id - Update an existing product by ID
- 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.
Top comments (1)
Great post! If someone needs a free MongoDB instance to test this out, go to the official Atlas site