5. Model-View-Controller Architecture
As your application grows in scale and scope, it is important to have a consistent approach for organizing its many components.
In this lesson, we'll learn how to implement one of the most popular patterns called the Model-View-Controller Architecture.
Table of Contents:
Organization and Separation of Concerns
In the last lesson, we built a RESTful API that lets users manage a list of fellows. They can:
(Create) Add a new fellow to the list
(Read) Get all fellows
(Read) Get a single fellow
(Update) Change the name of a fellow
(Delete) Remove a fellow from the list
In that application, all of the logic was built into the serer/index.js
file. While we have some organization within the file, separating the concerns of one file into multiple files will enable our application to scale without becoming a "monolith"
The Model-View-Controller (MVC) Architecture
While there are many approaches for organization and separation of concerns, one highly popular approach is called the Model-View-Controller (MVC) Architecture.

This architecture pattern organizes our code into three distinct pieces:
The Models are responsible for storing and managing the data of an application. They provide an interface (a set of methods) for interacting with that data in a predictable manner.
The Views are responsible for rendering the data of an application. They provide user-interfaces with buttons, forms, and other components that allow the user to see and request changes to the data.
The Controllers are responsible for managing interactions between the views and models. They process user inputs from the views, invoke the appropriate methods of the models, and send response back to the views to be updated.
Implementing a Model for MVC
With our current application structure, we already have clear separation between the views (our frontend React application) and the controllers (our Express endpoints/controllers).
However, our controllers and model are intertwined since our controllers directly interact with the database (the fellows
array). That is, they both handle the logic of parsing request inputs AND handle the logic of managing the data.
// Our "Database"
const fellows = [
{ name: 'Carmen', id: getId() },
{ name: 'Reuben', id: getId() },
{ name: 'Maya', id: getId() },
];
// Our Controllers
const createFellow = (req, res) => {
// Parse Inputs
const { fellowName } = req.body;
// Send Response
if (!fellowName) {
return res.status(400).send({ message: "Invalid Name" });
}
// Create a new fellow and add it to the "database"
const newFellow = {
name: fellowName,
id: getId()
}
fellows.push(newFellow)
// Send Response
res.status(201).send(newFellow);
}
const serveFellow = (req, res) => {
// Parse Inputs
const { id } = req.params;
// Find the fellow in the "database"
const fellow = fellows.find(fellow => fellow.id === Number(id));
// Send Response
if (!fellow) {
return res.status(404).send({
message: `No fellow with the id ${id}`
});
}
res.send(fellow);
};
// ... and more...
We need to create a separate model that focuses solely on managing the friends
database and provides methods for our controllers to use.
Server Organization
To create an MVC architecture, take a moment and build the following file structure for your server application.
server/
├── index.js
├── controllers/
│ └── fellowControllers.js
├── models/
│ └── Fellow.js
└── utils/
├── getId.js
└── handleFetch.js
index.js
builds theapp
, configures middleware, and sets the endpoints. However, the controllers are now imported.controllers/fellowControllers.js
defines all of the controllers for endpoints relating to fellow data. Each set of data should have its own controllers file.models/Fellow.js
defines a model for managing fellow data. This model is used exclusively by the fellow controllers. Each set of data managed by the server should have its own model.
By separating our code in this way, we show the separate "layers" of the application.
Before we build a model, do the following:
Move all of your controller functions into the
controllers/fellowControllers.js
fileExport them as an object using
module.exports = { /* list controller methods*/ }
Import the collection of controllers into
server/index.js
usingrequire(./controllers/fellowControllers.js)
Build a Model
To build a separate model layer to handle only data management logic, we will:
Make a
models/Fellow.js
file.Inside, export a
Fellow
withstatic
methods for each action needed by the controllers.Move the
fellows
"database" inside so the only way to access its data is through the model's interface.
// Restrict access to our mock "database" to just the Model
const fellows = [
{ name: 'Carmen', id: getId() },
{ name: 'Reuben', id: getId() },
{ name: 'Maya', id: getId() },
];
class Fellow {
// Create and add the new fellow to the "database" (the fellows array)
static create(name) {
const newFellow = {
name,
id: getId()
}
fellows.push(newFellow);
return newFellow;
}
// Get all values from the "database"
static getAll() {
return [...fellows];
}
// Get one value from the "database"
static find(id) {
return fellows.find((fellow) => fellow.id === id);
}
// Update one value from the "database"
static editName(id, newName) {
const fellow = Fellow.find(id);
if (!fellow) return null;
fellow.name = newName;
return fellow;
}
// Delete one value from the "database"
static delete(id) {
const fellowIndex = fellows.findIndex((fellow) => fellow.id === id);
if (fellowIndex < 0) return false;
fellows.splice(fellowIndex, 1);
return true;
}
}
module.exports = Fellow;
With the Fellow
model, we now leave managing the data to the model and let the control just focus on parsing the request and sending the response.
const Fellow = require('../models/Fellow.js');)
const createFellow = (req, res) => {
const { fellowName } = req.body;
if (!fellowName) {
return res.status(400).send({ message: "Invalid Name" });
}
const newFellow = Fellow.create(fellowName);
res.send(newFellow);
};
// Use `Fellow.find` to get the desired fellow
const serveFellow = (req, res) => {
const { id } = req.params;
const fellow = Fellow.find(Number(id));
if (!fellow) {
return res.status(404).send({
message: `No fellow with the id ${id}`
});
}
res.send(fellow);
};
Challenge
Build a Song
model and a server application for maintaining a playlist. Each song should have an id
, a title
, and an artist
(at minimum). The model should provide an interface for:
Creating a new song
Getting all songs
Getting a single song
Updating the title or artist of a song
Deleting a song
Then, create an endpoint and a controller for each of these pieces of functionality. The endpoints should follow REST conventions and should all begin with /api
Finally, build a frontend react application that can interact with the songs API that you've built. It should be able to:
Create: Add a new song to the list.
Read: Display a list of all songs.
Read: Display a single song.
Update: Update a single songs's title or artist.
Delete: Delete a single song.
Here is a recommended React Router page structure for your React app:
/
: The homepage which includes:Form for creating a new song
List of all songs
/songs/:id
: The details of a single song which includesThe title, artist, and ID of the song
A form to submit a new title or artist for the song
A button to delete the song from the list
Last updated