The Model-View-Controller (MVC) pattern is one of the most popular architectural patterns in web development. By dividing an application into three interconnected components — Model, View, and Controller — MVC promotes organized, maintainable, and scalable code. Node.js, with its asynchronous processing and vast ecosystem, is an excellent choice for building MVC-based applications, especially for web and API development. This guide explores the MVC pattern with Node.js, taking you through its benefits and a practical implementation.
What is the MVC Pattern?
The MVC pattern divides an application into three distinct parts:
- Model: Represents the data and business logic of the application. It interacts with the database and processes data independently of the user interface.
- View: Handles the presentation layer of the application. It displays data to the user and sends user commands to the Controller.
- Controller: Acts as an intermediary between the Model and the View. It takes user input, interacts with the Model, and updates the View accordingly.
This separation of concerns helps organize code in a way that’s easy to manage, test, and expand.
Benefits of Using MVC in Node.js
- Maintainability: MVC makes it easier to manage complex applications by organizing code into layers.
- Scalability: The structure allows individual components to be scaled or modified independently.
- Reusable Code: With MVC, components are often reusable across multiple views or parts of the application.
- Efficient Team Collaboration: Frontend and backend developers can work simultaneously with minimal overlap.
Setting Up an MVC Project with Node.js
Here, we’ll build a simple MVC application using Node.js and Express. Our example will be a basic "Task Manager" that allows users to view, add, and delete tasks.
Step 1: Initialize the Project
Start by creating a new Node.js project and installing necessary dependencies.
# Create a project folder
mkdir mvc-task-manager
cd mvc-task-manager
# Initialize Node.js
npm init -y
# Install Express and other dependencies
npm install express ejs mongoose body-parser
- Express: A minimalist web framework for Node.js, ideal for setting up controllers and routes.
- EJS: A templating engine that allows you to render dynamic HTML views.
- Mongoose: A popular library for MongoDB to model our data in the database.
- Body-parser: Middleware to parse incoming request bodies in a middleware.
Step 2: Set Up the Project Structure
Organize the application into models
, views
, and controllers
folders. This structure is essential for an MVC pattern.
mvc-task-manager/
│
├── models/
│ └── Task.js
│
├── views/
│ ├── index.ejs
│ └── tasks.ejs
│
├── controllers/
│ └── taskController.js
│
├── public/
│ └── css/
│ └── styles.css
│
├── app.js
└── package.json
Step 3: Configure the Model
The Model represents the data structure in your application. For this task manager, we’ll define a Task
model in MongoDB using Mongoose.
// models/Task.js
const mongoose = require('mongoose');
const taskSchema = new mongoose.Schema({
title: { type: String, required: true },
description: { type: String },
completed: { type: Boolean, default: false }
});
module.exports = mongoose.model('Task', taskSchema);
Here, taskSchema
defines our Task model with fields for title
, description
, and completed
.
Step 4: Create the Controller
The Controller handles the application logic, processes user inputs, interacts with the Model, and directs the View to render the data.
// controllers/taskController.js
const Task = require('../models/Task');
exports.getTasks = async (req, res) => {
const tasks = await Task.find();
res.render('tasks', { tasks });
};
exports.createTask = async (req, res) => {
const { title, description } = req.body;
await Task.create({ title, description });
res.redirect('/tasks');
};
exports.deleteTask = async (req, res) => {
await Task.findByIdAndDelete(req.params.id);
res.redirect('/tasks');
};
This controller defines three main actions:
-
getTasks
: Fetches all tasks from the database. -
createTask
: Adds a new task to the database. -
deleteTask
: Deletes a task by its ID.
Step 5: Set Up the View
In this example, we use EJS to render HTML views. This will allow us to display task data dynamically in the browser.
Create an index.ejs
file for the homepage and a tasks.ejs
file to display tasks.
<!-- views/index.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Task Manager</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<h1>Welcome to Task Manager</h1>
<a href="/tasks">View Tasks</a>
</body>
</html>
The tasks.ejs
file will render a list of tasks and provide forms for adding or deleting tasks.
<!-- views/tasks.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Task List</title>
</head>
<body>
<h1>Tasks</h1>
<ul>
<% tasks.forEach(task => { %>
<li><%= task.title %> - <a href="/delete/<%= task._id %>">Delete</a></li>
<% }) %>
</ul>
<form action="/add" method="POST">
<input type="text" name="title" placeholder="Task Title" required>
<input type="text" name="description" placeholder="Description">
<button type="submit">Add Task</button>
</form>
</body>
</html>
Step 6: Set Up Routes
Define routes in the main app.js
file to connect each URL endpoint to the relevant controller function.
// app.js
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const taskController = require('./controllers/taskController');
const app = express();
app.set('view engine', 'ejs');
mongoose.connect('mongodb://localhost:27017/taskDB', { useNewUrlParser: true, useUnifiedTopology: true });
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('public'));
// Routes
app.get('/', (req, res) => res.render('index'));
app.get('/tasks', taskController.getTasks);
app.post('/add', taskController.createTask);
app.get('/delete/:id', taskController.deleteTask);
const PORT = 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Step 7: Styling with CSS
To add a bit of styling, create a styles.css
file in the public/css/
folder. You can add basic styling to make your application visually appealing.
Step 8: Run and Test the Application
Start the application using:
node app.js
Visit http://localhost:3000
in your browser. You’ll be able to navigate between views, add new tasks, and delete tasks using the MVC architecture.
Best Practices for MVC Architecture in Node.js
- Keep Controllers Thin: Business logic should primarily reside in the Model.
- Use Middleware for Reusable Code: For instance, use middleware for authentication or request logging.
- Separate Routes from Controllers: To keep controllers clean and focused, consider defining routes in a separate file.
- Modularize Your Code: Keep models, views, and controllers in separate files and folders to maintain structure.