Lesson Overview

The MVC (Model View Controller) is a common design pattern used in software development. MVC separates the Model, View, and Controller code in order to all us to create more modular and robust applications. The Model refers to the data models used by the application (objects, classes, collections); the View refers to the user interface parts of your application that allow a user to interact with the program; the Controller contains the logic that performs the behind-the-scenes work, allowing the views to update and reflect changes to the model, performing tasks the user triggers on the view, etc.

In Node.js and Express, it's easiser to design more complex applications using MVC. You can phyiscally and logically separate code for your HTML pages, routing and request processing, and your data.

Pre-Requisites

Before doing this lesson, you should have gone through the lessons on Node.js Project Structure and Routing in Express. Also, if you'd like more background on MVC, have a read through The MVC Design Pattern.

Model View Controller in Express

In Node.js/Express application, we separate the Model, View, and Controller code as follows:

Templates

Templates are views that contain placeholders. When a template page is going to be sent in a response, the placeholders are filled with actual data. In an Express application, this is done with the help of a template engine. A template engine looks for placeholders using its preferred syntax, and helps your code replace the placeholders with actual data.

Express supports a few different template engines. I'm only going to talk about EJS (Embedded JavaScript) template engine, but you might be interested in learning about some of the others:

EJS is very simple and easy to learn: it's just JavaScript enclosed within <% and %> symbols for the most part. You just code your regular HTML, but place the code inside files with the .ejs extension and store them in the project's /views directory.

EJS commands are enclosed inside <% %> symbols as indicated below:

There are some other tags you can read about in the EJS documentation.

EJS will need to be added to a project as part of the npm install command.

Creating an Express MVC Application

Let's create a very basic MVC application with Express. Start a new project. I'm calling mine /express_mvc

Add sub-directories for /views and /controllers. We won't be adding a model, yet. We'll do that in the next example.

In the /views directory, add a file index.ejs. This will be an HTML file, but it must have the .ejs extension.

You can copy and paste the following code into your index.ejs file:

<!doctype html>
<html lang="en">
      
<head>
  <meta charset="utf-8">
  <title>Index Page</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>

<body>
  <header>
    <h1>Index Page</h1>
    <h2>Menu for <%=day%></h2>
  </header>

  <main>
    <p>menu for the selected day</p>
  </main>

  <footer>
    <address>&copy; 2023 Firstname Lastname</address>
  </footer>
</body>

</html>

In the HTML, you'll notice inside the level-1 heading is an EJS output tag:

<h2>Menu for <%=day%></h2>

The <%=day%> is a placeholder: the EJS engine will search for a variable or parameter called day, and it will replace <%=day%> with the value of the day variable.

Now we have to figure out how to pass the value for the day variable into the index.ejs template.

URL Parameters

One way we can pass a value to our application is by using a form input (such as a select box for the days of the week). However, that's not the only way! You can also pass values into an application by using a URL parameter. A URL parameter is a segment added to the URL, and the value of that segment is the value you want to pass to your program. For example, someone could load our application with the URL localhost:3000/menu/Tuesday or localhost:3000/menu/Saturday. We can grab the last segment as a parameter and pass it to the index.ejs template.

To get the value of the URL parameter, you add a segment to your app.use()/get()/post() URL pattern, such as /menu/:day. Notice that we use a colon : in front of the parameter's name. This adds the parameter as part of the request: request parameters are stored in the req.params object. The req.params object contains properties for any params in the URL, e.g. req.params.day.

Add an app.js to your project root and set it up:

"use strict";

  const express = require("express"),
      app = express();
  app.set("port", process.env.PORT || 3000);
  app.set("view engine", "ejs");
  
  app.listen(app.get("port"), () => {
      console.log(`Server running on http://localhost:${app.get("port")}`);
  });

The statement
app.set("view engine", "ejs");
sets the view engine to the EJS view engine. By default it's set to PUG, but we are using EJS so we have to add this statement to change it to EJS.

Now add a function to print the day URL parameter to the console:

app.get("/menu/:day", (req, res) => {
  console.log(`day: ${req.params.day}`);
});

Notice the URL pattern: /menu/:day. So this matches any URL that starts with /menu/ and has a value after it. Right now we'll assume it's a day of the week.

Inside the callback, we're just printing the req.params.day value to the console. This means we're not sending back a response, yet - so your browser will show an error when you run the app, and that's fine for now.

Initialize your app, then run npm install to install express and ejs:

npm install express ejs -s

Now you can run your program. Go to your browser and try:

localhost:3000/menu/Tuesday

Try different days (you can actually use any string in place of "Tuesday". You should see your last URL segment in the console.

Now let's finish our application so that the day of the week actually shows up in the browser instead of the console. This requires a controller.

Adding a Controller

The controller's job is to manage the processing behind the scenes. In this case, our controller will retrieve the day value from the request parameters and send it to the index.ejs file, which will then be sent in the response.

In your /controllers directory, add the file homeController.js. It's common to have multiple controllers, and most developers name the main controller either homeController.js or mainController.js.

Add the strict mode statement to your controller.

The controller is a module, so we want to expose a function called renderMenu that can get the parameter value from the request object and pass it to the view index.ejs. Let's expose our function by assigning it to exports:

exports.renderMenu = (req, res) => {

};

This module function needs the request and response object, so they have been added as paramters. It needs the request object so that it can retrieve req.params.day and it needs the response object so it can render the index.ejs page containing teh day value and send that as a response.

First, let's add the statement that retrieves the day parameter:

const paramDay = req.params.day;

To add the day value to the index.ejs template, we use the res.render() function. res.render() accepts two arguments:

res.render() will also automatically set the response status code, content type, and will write the final rendered version of the template .ejs file to the response body, then it will send the response for you! It does a lot of the work we usually have to do ourselves. This means we can add one more statement to our controller to complete it:

res.render("index", {day: paramDay});

This takes the value of our paramDay variable and assigns it to the "day" key, which makes "day" a local variable in the index.ejs file. This is essentially how you can pass a value from your request to your controller to your EJS template response.

Now we need to update our app.get(): the callback should now be the controller function we just exposed:

First, we need to require() the controller in our app.js:

const homeController = require("./controllers/homeController");

Now we can update our app.get(): the callback is now going to be the homeController's renderMenu() function instead of the custom callback we had previously:

app.get("/menu/:day", homeController.renderMenu);

Now run your program if it's not already running, and try the URL again with any day of the week:

localhost:3000/menu/Saturday

(or whatever day you like)

output in the browser
Output for localhost:3000/menu/Saturday

Adding a Model

Now let's make it a true MVC program by adding a model. Grab the menu.json file from GitHub and add it to your project inside the /models directory.

Examine the JSON file: you'll see it's a list of objects where each object's key is the day of the week in title case (e.g. Tuesday, Wednesday, etc). Each object is a simple menu for three meals: breakfast, lunch, and dinner. When the user visits a URL for a specific day of the week, we will display a level-3 heading for each meal with a list of the items, as shown in the screen shot below:

level 3 headings for breakfast/lunch/dinner, each heading shows a paragraph with the contents for that meal item
Proposed output for our MVC program

Our controller's renderMenu() function needs to know which menu object to add to the list of locals for the index.ejs file. To do this, we must first import the menu.json file into our controller:

const menu = require("../models/menu.json");

Note the path: the controller is in the /express_mvc/controllers directory but the menu.json is in the /express_mvc/models directory, so we have to go up one level to /express_mvc, then into /models in order to get the menu.json file.

Now we can update the renderMenu() function by getting the menu object for the specific day. The menu variable contains the list of objects, keyed by day of the week in title case. So we can use req.params.day as the index to menu:

let paramMenu = menu[req.params.day];

Then we have to add the paramMenu's value to a "menu" parameter to be sent to the index page as a local variable:

res.render("index", { day: paramDay, menu: paramMenu});

All that's left is to update the index.ejs view. Add the code in the <main> to iterate through the menu object keys and print a level-3 heading for each property (breakfast/lunch/dinner) and a paragraph containing the value for each property:

<% for (let m in menu) { %>
  <h3><%=m%></h3>
  <p><%=menu[m]%></p>
<% } %>

Now re-run your program and try each day of the week (make sure you use title case). What happens if you don't enter a valid day of the week?

Express MVC with a Form

Let's try an example with a form. In a previous lesson you wrote a program that displayed the Chinese Zodiac animal for a specific year. Let's rewrite this as an MVC program using Express.

Add a new project. I'm calling mine express_zodiac. Add the /views and /controllers directory. We don't need a model for this project.

Add these two views in your /views directory:

Our form is going to ask for a user's name and the year. So we'll need to handle a request to load the index page.

the input form, name entered as Arti, year entered as 2009
The form on the index page

When the user submits the form, we want to see the following output:

output shows Welcome Arti header, then paragraph Arti, you were born in 2009, which is year of the Ox.
The output of the program

Once the user submits the form with the two inputs, we'll need that request to be handled as a POST request. This request will have a few steps:

  1. Get the request body as url-encoded data.
  2. Retrieve the animal for the user-entered year.
  3. Render the result.ejs with the user's name and the zodiac year.

Each of these will be its own middleware function.

For this project, we should also separate our code into two controllers. One homeController, which manages the rendering of the EJS templates, and one animalController, which uses the third-party chinese-year package to get the zodiac animal for a specific year. This allows us to re-use the animalController in other programs!

Add the two controllers to the /controllers directory: homeController.js and animalController.js or call them whatever makes sense to you (although I do thing the name "animalController" is kind of funny :D )

Animal Controller

The animalController will have one exposed function that does pretty much what we had in our previous zodiac program: get the year, make sure if the year is empty that we assign the current year, then use the year in the getAnimal() endpoint of the chinese-year package.

To make the animal available to the next middleware function, we can add it as a parameter in the request body's query string: that's a common way of passing data from one middleware function to another, and it's totally fine: we'll just add a new item to the request body for the animal we found.

Also remember that in this case, it's from a POST request, so the query string is accessed via req.body, not req.query.

"use strict";
// /controllers/animalController.js
const chineseYear = require("chinese-year");

exports.getAnimal = (req, res, next) => {
    // make sure the year input has an actual year in it
    if (!req.body.year) {
        req.body.year = new Date().getFullYear();
    }
    // get the animal for the year
    const animal = chineseYear.getAnimal(req.body.year);
    // add the animal to the query string
    req.body.animal = animal;
    // next middleware
    next();
}

Home Controller

Our homeController handles the rendering of the index page and also the results page when the form has been submitted. So this controller needs two functions exposed: one to render the index page and one to render the results page.

The index page function is simple, since it has no placeholders:

exports.renderIndex = (req, res) => {
  res.render("index");
};

In fact, this doesn't even need to be an .ejs file, but we'll tackle that issue in an upcoming lesson.

Our function that renders the results page needs to pass three locals to results.ejs:

exports.renderResult = (req, res) => {
  res.render("result", { 
      username: req.body.username,
      year: req.body.year, 
      animal: req.body.animal
  });
}

Now we can put it all together in the app.js file. Add the app.js to your project root if you haven't already done so.

app.js

Add the code to app.js to:

"use strict";

  const express = require("express"),
      app = express(),
      animalController = require("./controllers/animalController"),
      homeController = require("./controllers/homeController");
  
app.set("port", process.env.PORT || 3000);
app.set("view engine", "ejs");

// rest of code will go here

app.listen(app.get("port"), () => {
  console.log(`Server running on http://localhost:${app.get("port")}`);
});

Recall that we need a function to handle requests to the index page. Let's use /zodiac/index, but feel free to be a bit more creative (e.g. with or without .html)

// route get requests to index page
app.get("/zodiac/index", homeController.renderIndex);

As we discussed, we need three middleware functions to handle the POST request for form submission:

First, populate req.body with the query string in the request body:

app.use(
  express.urlencoded({
    extended: false
  })
);

Second, get the zodiac animal:

app.use("/zodiac/getyear", animalController.getAnimal);

Finally, we can render the result page:

app.post("/zodiac/getyear", homeController.renderResult);

If you haven't done so already, initialize your app. Then make sure you install all three modules/packages via npm install:

npm install chinese-year express ejs -s

Now you can run your program. Load the index page in your browser, e.g.
localhost:3000/zodiac/index

Fill in your name and birth year, then click the submit button.

You should see the output in the browser!

Exercises

  1. Write an MVC Express application that displays information about a circle with a specific radius.

    Use this index.ejs file as a base and put it in your /views directory.

    form with value 2 entered for radius
    The form asks for a radius

    When the user clicks the submit button, the page should reload with the circle information in the first <section> element.

    form with value 3 entered for radius
    The form asks for a radius

    Add the circle.js object/class as the model for your application (e.g. it should go in /models). Your circle should have a radius property, two accessor properties circumference and area, and a toString() function property. If the radius is not provided when someone creates a new circle object, use a default value of 0.

    Add a single controller with three exposed functions:

    • renderIndex() renders the index page.
    • getCircle() uses the inputted radius from the query string and uses it to contruct a new Circle object. If radius is empty, construct a default circle.
    • renderResult() passes the circle to the index.ejs page and renders it.

    Modify your index.ejs page: Inside the empty first <section> element, add the code to do the following if a circle object exists as a local:

    • render a level-2 heading with the content "Circle Information".
    • below the heading, display the circle (it should automatically use the toString() if you coded it correctly) in a paragraph element.
    • below the circle object, display a paragraph element containing "Circumference: " followed by the circle circumference
    • below the circle object, display a paragraph element containing "Area: " followed by the circle area

    TIP: when you render the index page, pass in a null circle object.

    output shows circle with radius 2, circumference 18.84, area 28.27 except with way more decimal places
    The output with circle information