Introduction
The MVC pattern helps organize a Node.js application into separate parts: Model, View, and Controller. Instead of keeping database logic, application logic, and page responses inside one large file, MVC separates these responsibilities. In this chapter, you will practice MVC step by step, starting with a basic structure and gradually building controllers, models, routes, views, middleware, and a simple CRUD-style application using Node.js and Express.js. Node.js MVC Pattern practice questions with solutions help to build concepts.
Question 1: What is the basic MVC folder structure in Node.js?
Problem
Create a simple Node.js project using the MVC pattern.
Solution
Create a project:
mkdir mvc-app
cd mvc-app
npm init -y
npm install express
Create this folder structure:
mvc-app/
│
├── controllers/
│ └── userController.js
│
├── models/
│ └── userModel.js
│
├── routes/
│ └── userRoutes.js
│
├── views/
│ └── userView.js
│
├── app.js
└── package.json
Step-by-Step Explanation
MVC stands for:
M = Model
V = View
C = Controller
Model
The Model manages data and data-related operations.
Example:
models/
└── userModel.js
View
The View is responsible for what the user sees.
Example:
views/
└── userView.js
Controller
The Controller contains application logic and connects requests with models and views.
Example:
controllers/
└── userController.js
Routes
Routes decide which controller should handle a request.
routes/
└── userRoutes.js
Basic MVC Flow
Client
↓
Route
↓
Controller
↓
Model
↓
Controller
↓
View / Response
↓
Client
Important Point
MVC is an architectural pattern. It is a way of organizing application responsibilities rather than a special Node.js feature.
Question 2: How do you create a Model in an MVC application?
Problem
Create a simple user Model that contains user data and a function for retrieving users.
Solution
Create:
models/userModel.js
Add:
const users = [
{
id: 1,
name: "Rahul"
},
{
id: 2,
name: "Priya"
}
];
function getUsers() {
return users;
}
module.exports = {
getUsers
};
Step-by-Step Explanation
First, we create sample data:
const users = [
{
id: 1,
name: "Rahul"
},
{
id: 2,
name: "Priya"
}
];
Then create a function:
function getUsers() {
return users;
}
Finally export it:
module.exports = {
getUsers
};
Now another file can use the Model.
Question 3: How do you create a Controller in Node.js MVC?
Problem
Create a Controller that gets users from the Model and sends them as JSON.
Solution
Create:
controllers/userController.js
Add:
const userModel =
require("../models/userModel");
function getUsers(
req,
res
) {
const users =
userModel.getUsers();
res.json({
success: true,
users: users
});
}
module.exports = {
getUsers
};
Step-by-Step Explanation
Import the Model:
const userModel =
require("../models/userModel");
Call the Model:
const users =
userModel.getUsers();
Send the response:
res.json({
success: true,
users: users
});
Why use a Controller?
Without MVC, you might put everything inside app.js.
With MVC:
Route
↓
Controller
↓
Model
Each part has a clear responsibility.
Question 4: How do you create MVC routes?
Problem
Create a route that sends /users requests to the user Controller.
Solution
Create:
routes/userRoutes.js
Add:
const express = require("express");
const router =
express.Router();
const userController =
require("../controllers/userController");
router.get(
"/users",
userController.getUsers
);
module.exports = router;
Now create app.js:
const express = require("express");
const userRoutes =
require("./routes/userRoutes");
const app = express();
app.use(
userRoutes
);
app.listen(3000, () => {
console.log(
"Server running on port 3000"
);
});
Test
Open:
http://localhost:3000/users
Response
{
"success": true,
"users": [
{
"id": 1,
"name": "Rahul"
},
{
"id": 2,
"name": "Priya"
}
]
}
Request Flow
GET /users
↓
userRoutes.js
↓
userController.getUsers
↓
userModel.getUsers()
↓
JSON Response
Important Point
Routes should generally focus on mapping URLs and HTTP methods to controller functions rather than containing large amounts of business logic.
Question 5: How do you create a View in a Node.js MVC application?
Problem
Create a simple View that displays a list of users.
Solution
For a simple beginner example, create:
views/userView.js
Add:
function userListView(users) {
let html =
"<h1>User List</h1>";
html += "<ul>";
users.forEach(user => {
html += `
<li>
${user.name}
</li>
`;
});
html += "</ul>";
return html;
}
module.exports = {
userListView
};
Update the Controller:
const userModel =
require("../models/userModel");
const userView =
require("../views/userView");
function getUsers(
req,
res
) {
const users =
userModel.getUsers();
const html =
userView.userListView(users);
res.send(html);
}
module.exports = {
getUsers
};
Test
Open:
http://localhost:3000/users
You will see:
User List
• Rahul
• Priya
Step-by-Step Explanation
The Model provides data:
const users =
userModel.getUsers();
The View converts the data into HTML:
const html =
userView.userListView(users);
The Controller sends the HTML:
res.send(html);
MVC Flow
Model
↓
Data
↓
Controller
↓
View
↓
HTML Response
Important Point
In production applications, template engines such as EJS, Pug, or Handlebars are often used instead of manually creating HTML strings.
Question 6: How do you create an MVC application for getting a single user?
Problem
Create an API:
GET /users/:id
that returns one user.
Solution
Step 1: Update the Model
models/userModel.js
const users = [
{
id: 1,
name: "Rahul"
},
{
id: 2,
name: "Priya"
},
{
id: 3,
name: "Amit"
}
];
function getUsers() {
return users;
}
function getUserById(id) {
return users.find(
user =>
user.id === id
);
}
module.exports = {
getUsers,
getUserById
};
Step 2: Create Controller
controllers/userController.js
const userModel =
require("../models/userModel");
function getUserById(
req,
res
) {
const id =
Number(req.params.id);
const user =
userModel.getUserById(id);
if (!user) {
return res.status(404).json({
success: false,
message:
"User not found."
});
}
res.json({
success: true,
user: user
});
}
module.exports = {
getUserById
};
Step 3: Create Route
routes/userRoutes.js
const express = require("express");
const router =
express.Router();
const userController =
require("../controllers/userController");
router.get(
"/users/:id",
userController.getUserById
);
module.exports = router;
Step 4: Test
Open:
http://localhost:3000/users/2
Response
{
"success": true,
"user": {
"id": 2,
"name": "Priya"
}
}
If User Does Not Exist
Open:
http://localhost:3000/users/99
Response:
{
"success": false,
"message": "User not found."
}
Important Point
req.params.id is a string. In this example, Number() converts it to a number before the Model searches the array.
Question 7: How do you add a new user using the MVC pattern?
Problem
Create a POST API that adds a new user using:
POST /users
Solution
Model
Update models/userModel.js:
const users = [
{
id: 1,
name: "Rahul"
},
{
id: 2,
name: "Priya"
}
];
function getUsers() {
return users;
}
function addUser(name) {
const newUser = {
id:
users.length + 1,
name:
name
};
users.push(
newUser
);
return newUser;
}
module.exports = {
getUsers,
addUser
};
Controller
Update controllers/userController.js:
const userModel =
require("../models/userModel");
function createUser(
req,
res
) {
const {
name
} = req.body;
if (
!name ||
name.trim() === ""
) {
return res.status(400).json({
success: false,
message:
"Name is required."
});
}
const user =
userModel.addUser(
name.trim()
);
res.status(201).json({
success: true,
message:
"User created successfully.",
user:
user
});
}
module.exports = {
createUser
};
Route
router.post(
"/users",
userController.createUser
);
app.js
Make sure JSON parsing is enabled:
const express = require("express");
const userRoutes =
require("./routes/userRoutes");
const app = express();
app.use(
express.json()
);
app.use(
userRoutes
);
app.listen(3000, () => {
console.log(
"Server running on port 3000"
);
});
Test Request
POST http://localhost:3000/users
JSON:
{
"name": "Amit"
}
Response
{
"success": true,
"message": "User created successfully.",
"user": {
"id": 3,
"name": "Amit"
}
}
MVC Flow
POST /users
↓
Route
↓
Controller
↓
Model
↓
Create User
↓
Controller
↓
JSON Response
Important Point
The Controller validates the incoming request, while the Model handles the data operation.
Question 8: How do you separate validation middleware from an MVC Controller?
Problem
Create middleware that validates a user’s name before the request reaches the Controller.
Solution
Create:
middleware/
└── validateUser.js
Add:
function validateUser(
req,
res,
next
) {
const {
name
} = req.body;
if (
typeof name !== "string" ||
name.trim() === ""
) {
return res.status(400).json({
success: false,
message:
"Name is required."
});
}
next();
}
module.exports = {
validateUser
};
Update the route:
const express = require("express");
const router =
express.Router();
const userController =
require("../controllers/userController");
const {
validateUser
} = require("../middleware/validateUser");
router.post(
"/users",
validateUser,
userController.createUser
);
module.exports = router;
Step-by-Step Explanation
The request first reaches:
POST /users
Then:
validateUser
checks the data.
If invalid:
400 Bad Request
If valid:
next();
passes control to:
userController.createUser
Complete Flow
Client
↓
Route
↓
Validation Middleware
↓
Valid?
↙ ↘
No Yes
↓ ↓
400 Controller
↓
Model
↓
Response
Important Point
Separating validation into middleware can keep Controllers smaller and easier to maintain.
Question 9: How do you connect MongoDB with an MVC Node.js application?
Problem
Create a basic MVC structure where the Model communicates with MongoDB.
Solution
Install Mongoose:
npm install mongoose
Create:
models/userModel.js
Add:
const mongoose =
require("mongoose");
const userSchema =
new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
}
});
const User =
mongoose.model(
"User",
userSchema
);
async function getUsers() {
return await User.find();
}
async function createUser(
data
) {
return await User.create(
data
);
}
module.exports = {
getUsers,
createUser
};
Database Connection
Create:
config/database.js
Add:
const mongoose =
require("mongoose");
async function connectDatabase() {
await mongoose.connect(
process.env.MONGO_URI
);
console.log(
"MongoDB connected."
);
}
module.exports =
connectDatabase;
Controller
const userModel =
require("../models/userModel");
async function getUsers(
req,
res,
next
) {
try {
const users =
await userModel.getUsers();
res.json({
success: true,
users:
users
});
} catch (error) {
next(error);
}
}
async function createUser(
req,
res,
next
) {
try {
const user =
await userModel.createUser(
req.body
);
res.status(201).json({
success: true,
user:
user
});
} catch (error) {
next(error);
}
}
module.exports = {
getUsers,
createUser
};
app.js
const express =
require("express");
const connectDatabase =
require("./config/database");
const userRoutes =
require("./routes/userRoutes");
const app = express();
app.use(
express.json()
);
connectDatabase()
.then(() => {
app.listen(
3000,
() => {
console.log(
"Server running on port 3000"
);
}
);
})
.catch(error => {
console.error(
"Database connection failed:",
error
);
});
Step-by-Step Explanation
The Model contains the database logic:
User.find()
and:
User.create()
The Controller calls the Model:
await userModel.getUsers();
The Route connects the URL to the Controller.
MVC Architecture
Routes
↓
Controllers
↓
Models
↓
MongoDB
Then the result travels back:
MongoDB
↓
Model
↓
Controller
↓
Response
Important Point
Keeping database operations inside Models prevents database-specific logic from spreading throughout your route files.
Question 10: How do you build a complete MVC CRUD API?
Problem
Build a beginner-friendly CRUD API using the MVC pattern.
The API should support:
GET /users
GET /users/:id
POST /users
PUT /users/:id
DELETE /users/:id
Solution
For this learning example, we will use an array instead of a database.
Project Structure
mvc-app/
│
├── controllers/
│ └── userController.js
│
├── models/
│ └── userModel.js
│
├── routes/
│ └── userRoutes.js
│
├── app.js
└── package.json
Step 1: Model
models/userModel.js
let users = [
{
id: 1,
name: "Rahul",
email: "rahul@example.com"
},
{
id: 2,
name: "Priya",
email: "priya@example.com"
}
];
function getUsers() {
return users;
}
function getUserById(
id
) {
return users.find(
user =>
user.id === id
);
}
function createUser(
data
) {
const newUser = {
id:
users.length > 0
? users[users.length - 1].id + 1
: 1,
name:
data.name,
email:
data.email
};
users.push(
newUser
);
return newUser;
}
function updateUser(
id,
data
) {
const user =
getUserById(id);
if (!user) {
return null;
}
if (
data.name !== undefined
) {
user.name =
data.name;
}
if (
data.email !== undefined
) {
user.email =
data.email;
}
return user;
}
function deleteUser(
id
) {
const userIndex =
users.findIndex(
user =>
user.id === id
);
if (
userIndex === -1
) {
return null;
}
const deletedUser =
users.splice(
userIndex,
1
);
return deletedUser[0];
}
module.exports = {
getUsers,
getUserById,
createUser,
updateUser,
deleteUser
};
Step 2: Controller
controllers/userController.js
const userModel =
require("../models/userModel");
function getUsers(
req,
res
) {
const users =
userModel.getUsers();
res.json({
success: true,
users:
users
});
}
function getUserById(
req,
res
) {
const id =
Number(req.params.id);
const user =
userModel.getUserById(id);
if (!user) {
return res.status(404).json({
success: false,
message:
"User not found."
});
}
res.json({
success: true,
user:
user
});
}
function createUser(
req,
res
) {
const {
name,
email
} = req.body;
if (
!name ||
!email
) {
return res.status(400).json({
success: false,
message:
"Name and email are required."
});
}
const user =
userModel.createUser({
name:
name.trim(),
email:
email.trim()
});
res.status(201).json({
success: true,
message:
"User created successfully.",
user:
user
});
}
function updateUser(
req,
res
) {
const id =
Number(req.params.id);
const user =
userModel.updateUser(
id,
req.body
);
if (!user) {
return res.status(404).json({
success: false,
message:
"User not found."
});
}
res.json({
success: true,
message:
"User updated successfully.",
user:
user
});
}
function deleteUser(
req,
res
) {
const id =
Number(req.params.id);
const user =
userModel.deleteUser(id);
if (!user) {
return res.status(404).json({
success: false,
message:
"User not found."
});
}
res.json({
success: true,
message:
"User deleted successfully.",
user:
user
});
}
module.exports = {
getUsers,
getUserById,
createUser,
updateUser,
deleteUser
};
Step 3: Routes
routes/userRoutes.js
const express =
require("express");
const router =
express.Router();
const userController =
require("../controllers/userController");
router.get(
"/users",
userController.getUsers
);
router.get(
"/users/:id",
userController.getUserById
);
router.post(
"/users",
userController.createUser
);
router.put(
"/users/:id",
userController.updateUser
);
router.delete(
"/users/:id",
userController.deleteUser
);
module.exports = router;
Step 4: App
app.js
const express =
require("express");
const userRoutes =
require("./routes/userRoutes");
const app = express();
app.use(
express.json()
);
app.use(
"/api",
userRoutes
);
app.listen(
3000,
() => {
console.log(
"Server running on port 3000"
);
}
);
Step 5: Test GET All Users
Request:
GET http://localhost:3000/api/users
Response:
{
"success": true,
"users": [
{
"id": 1,
"name": "Rahul",
"email": "rahul@example.com"
},
{
"id": 2,
"name": "Priya",
"email": "priya@example.com"
}
]
}
Step 6: Test GET One User
Request:
GET http://localhost:3000/api/users/1
Step 7: Test POST
Request:
POST http://localhost:3000/api/users
JSON:
{
"name": "Amit",
"email": "amit@example.com"
}
Step 8: Test PUT
Request:
PUT http://localhost:3000/api/users/1
JSON:
{
"name": "Rahul Kumar"
}
Step 9: Test DELETE
Request:
DELETE http://localhost:3000/api/users/2
Complete MVC Flow
CLIENT
|
↓
ROUTES
|
↓
CONTROLLER
|
↓
MODEL
|
↓
DATA / DATABASE
|
↓
MODEL
|
↓
CONTROLLER
|
↓
RESPONSE
|
↓
CLIENT
Important Point
This example uses an array, so data will disappear when the Node.js process restarts. In a real application, the Model would normally communicate with a persistent database.
Key Takeaways
1. MVC means Model, View, and Controller
MVC separates different responsibilities of an application.
2. Model handles data
The Model is responsible for data-related operations such as:
Create
Read
Update
Delete
3. Controller handles application logic
Controllers receive requests, call Models, process results, and prepare responses.
4. View handles presentation
The View is responsible for presenting information to the user.
5. Routes connect URLs to Controllers
For example:
router.get(
"/users",
userController.getUsers
);
6. MVC makes applications easier to organize
Instead of putting everything into app.js, responsibilities are divided into separate files.
7. Models can communicate with databases
For example, a Model can use Mongoose to communicate with MongoDB.
8. Middleware can be separated from Controllers
Validation, authentication, logging, and other reusable tasks can be placed in middleware.
9. Controllers should not become giant files
If a Controller contains too much logic, some responsibilities can be moved into services or other application layers.
10. MVC is an architectural pattern
Node.js does not force you to use MVC. You choose it as an application organization pattern.
11. req.params contains route parameters
For:
/users/10
you can access:
req.params.id
12. req.body contains request body data
When JSON middleware is enabled:
app.use(express.json());
you can access:
req.body
13. CRUD works naturally with MVC
A typical MVC API can separate CRUD operations between:
Routes
Controllers
Models
14. MVC can work with REST APIs
MVC is commonly used to organize Express.js REST API projects.
15. MVC is useful for larger applications
As an application grows, separating responsibilities can make the code easier to understand and maintain.
16. Use proper database storage in production
An in-memory JavaScript array is useful for learning but should not be used as the main data store for a production application.
17. Error handling should be separated
Large applications can use centralized Express error-handling middleware.
18. Authentication can fit into MVC
Authentication middleware can protect routes before requests reach Controllers.
19. Validation should happen before database operations
Incoming data should be validated before the Model stores or processes it.
20. MVC is not the only architecture
Larger Node.js applications may also use patterns such as service layers, repositories, or clean architecture alongside or instead of traditional MVC.
FAQs
1. What is MVC in Node.js?
MVC stands for Model, View, and Controller. It is an architectural pattern used to organize a Node.js application into separate responsibilities.
The Model handles data, the View handles presentation, and the Controller handles application logic.
2. What is the Model in Node.js MVC?
The Model handles data-related operations.
For example, a user Model may contain functions such as:
getUsers();
getUserById();
createUser();
updateUser();
deleteUser();
In a real application, the Model may communicate with MongoDB, PostgreSQL, MySQL, or another database.
3. What is a Controller in Node.js MVC?
A Controller receives the request, performs application-level logic, communicates with the Model, and sends the response.
For example:
async function getUsers(
req,
res
) {
const users =
await userModel.getUsers();
res.json({
users
});
}
4. What is a View in Node.js MVC?
A View is responsible for presenting data to the user.
For server-rendered applications, the View may be an HTML template created using a template engine such as EJS, Pug, or Handlebars.
For a REST API, there may be no traditional server-side View because the API commonly returns JSON.
5. Why should I use MVC in Node.js?
MVC can make a project easier to organize as it grows.
Instead of placing routes, database queries, validation, and responses into one large file, responsibilities can be separated.
6. Is MVC required in Express.js?
No. Express.js does not require MVC.
You can create a small Express application in one file. MVC becomes useful when you want clearer separation of responsibilities and your project becomes larger.
7. What is the difference between MVC and REST API?
MVC is an application architecture pattern.
REST is a style of designing network APIs around resources, HTTP methods, and representations.
They can be used together:
REST API
↓
MVC Architecture
↓
Routes
Controllers
Models
Written by Shubhranshu Shekhar, who has trained 20000+ students in coding.
