A Comprehensive Guide To Building A Node JS MVC Application

Reading Time: 18 minutes

Quick Summary :- In this comprehensive guide, we have talked about building a NodeJS MVC application in detail. Starting with MVC and its architecture pattern, we have talked about each step in detail to build a MVC application with NodeJS. Read more in detail.

eSparkbiz Technologies Pvt Ltd
Chintan Gor (CTO, eSparkBiz)

The main point is you shouldn’t neglect architecture and begin implementing your project as per the architecture. By doing this, you face less issues in scaling your app in future.

But, creating a fully-functional application will be challenging if you don’t have a proper understanding of the architecture. Hence, it’s highly important for any Node JS Development Company to understand the architecture.

First, we will walk you through Node JS MVC application and then provide a step-by-step process to build Node.js MVC application.
MVC-Architecture

Understanding The Node.js MVC Architecture Pattern

Node.js is not only a framework, it’s a cross-platform JavaScript runtime environment that is specially built to deal with the server. Moreover, Node JS MVC allows you to include JavaScript on the client as well as server-side.

Using the Node.js, you have an opportunity to utilize JavaScript over all the three components of MVC. Here, the model operates in your server & view in the browser; however, both of them are written in JavaScript.

With each passing year, a vast number of Node.js frameworks are released in the market. Therefore, by choosing the best frameworks and combining it with the standard Node JS MVC pattern can help you develop excellent web applications.

Here are some of the top Node.js frameworks in the market:

1. Express : Even though it’s small and adaptable, using Express MVC you can easily manage big applications.

2. js : JS offers a wide range of plugins that you can use in your project. Also, it emphasizes more than authenticity and stability.

3. Mojito : Mojito a suitable framework for building mobile applications.

4. Koa : This framework utilizes generators to enhance error handling, removes middles and helps you to build a high-performance app.

In fact, there has been an ongoing battle between Express and Koa for a long time.

Based on the requirement of your project, you should choose the framework.

As of now, for our Node JS MVC Example, we will be utilizing the Hapi.js framework. You can also analyze the Express JS Model View Controller for knowledge purposes.

Building & Structuring Node.js MVC App

Laying out the Foundation

Initially, you have to create a package.json file for developing any Node.js MVC app that will consist of all the dependencies and scripts. Rather than forming this file manually, npm can carry out this task for us via the init command:

mkdir notes-board

cd notes-board

npm init -y

As soon as the procedure is completed, you will possess a package.json file for use.

For the purpose of this tutorial, we will include the Hapi.js framework. It offers a perfect chord between simplicity, stability, and features that work fine for Node JS MVC app.

npm install @hapi/[email protected]

This command will load the Hapi.js file and include it in our package.json file as a dependency.

Now, in the next step, build a fresh file and the web server will start everything. Don’t wait and form a server.js file in the app directory and include the code to it:

"use strict";

const Hapi = require("@hapi/hapi");

const Settings = require("./settings");

const init = async () => {

  const server = new Hapi.Server({ port: Settings.port });

  server.route({

    method: "GET",

    path: "/",

    handler: (request, h) => {

      return "Hello, world!";

    }

  }); 

  await server.start();

  console.log(`Server running at: ${server.info.uri}`);

};

process.on("unhandledRejection", err => {

  console.log(err);

  process.exit(1);

});

init();

The above code would lay the basics of the entire application. For that purpose, knowing the Architecture of Node.js could be extremely vital.

Initially, we declare here that we would be choosing to go with the strict mode, which is the common practice while utilizing the Hapi.js framework.

Now, we integrate our dependencies and represent it like a new server object where we keep the connection port to 3000 (the port could be any number between 1023 and 65535).

Our server’s initial path will work as a test to see if all the things are working correctly; hence, a “Hello World” message would work fine for us.

For all the routes, we have to mention the HTTP method and path (URL) which it would reply to and a handler, that’s a function that would process the HTTP request.

The handler function can look after two arguments: request and h. The first consists of information about the HTTP call and the second will offer us some effective ways to tackle our response to that call.

Ultimately, we initiate our server with the server.start() method.

Storing Our Settings

It’s an effective procedure to keep the configuration variables in a dedicated file. Then this file exports a JSON object consisting of our data, where every key is appointed from an environment variable, however, without neglecting the fallback value.

In this file, we have the option to keep settings based on the environment (like production).  For instance, we can possess an in-memory sample of SQLite for development tasks; however, a real SQLite database file is in production.

Choosing the settings based on the existing environment is pretty simple. As we also consist of an env variable in our file that will consist of either development or production, we can execute the code given below to obtain the database settings:

const dbSettings = Settings[Settings.env].db;

Hence, dbSettings will consist of an in-memory database setting. The env variable is in development or would have a database file route, whereas the env variable is in production.

Moreover, we could enable support for the .env file while storing our environment variables in the system for development purposes.

This is achieved utilizing the package like dotenv for Node.js MVC app, which will read the .env file from the root of our project and instantly include the discovered values to the environment.

Please remember: in case you also choose to utilize a .env file, ensure that to install the package with install dotenv and include it to .gitignore; hence you don’t publish any kind of sensitive information.

Our settings.js file will consist of things as follows:

// This will load our .env file and add the values to process.env,

// IMPORTANT: Omit this line if you don't want to use this functionality

require("dotenv").config({ silent: true });

module.exports = {

  port: process.env.PORT || 3000,

  env: process.env.NODE_ENV || "development",

  // Environment-dependent settings

  development: {

    db: {

      dialect: "sqlite",

      storage: ":memory:"

    }

  },

  production: {

    db: {

      dialect: "sqlite",

      storage: "db/database.sqlite"

    }

  }

};

Now we can begin with our application by running the command given below and opening the http://localhost: 3000 in our web browser:

node server.js

Please remember: this whole project was tested on Node v12.15.0. In case you are facing any issues, make sure that you modify the updated installation.

Defining the Routes

Defining the Routes

The definition of routes offers a synopsis of the functionality supported by our application. To form new routes, we only need to duplicate the route structure that we have in our server.js file, updating each one’s content.

Let’s begin by forming a new directory known as lib in our project. Now, we will integrate all the JS elements. That’s where Node.js Application Architecture plays an integral part.

Inside lib, let’s form a route.js file and include the snippet given below:

"use strict";

const Path = require("path");

module.exports = [

  // we’re going to define our routes here

];

In this file, we would supply a set of objects that consist of every route of our application. To mention the initial path, include the following object to the array:

{

  method: "GET",

  path: "/",

  handler: (request, h) => {

    return "All the notes will appear here";

  },

  config: {

    description: "Gets all the notes available"

  }

},

Our initial path is for the homepage (/), and as it will just return information, we assign it to the GET method.

As of now, it will display a message “All the notes will appear here,” which will update later for the controller function. The description field inside the config function is especially for the documentation process.

Now, we will form the four paths for our notes inside the /note/ path. Considering we are developing a CRUD application, we will require one separate path for every action with the relevant HTTP methods.

Include the following definitions next to the earlier route:

{

  method: "POST",

  path: "/note",

  handler: (request, h) => {

    return "New note";

  },

  config: {

    description: "Adds a new note"

  }

},

{

  method: "GET",

  path: "/note/{slug}",

  handler: (request, h) => {

    return "This is a note";

  },

  config: {

    description: "Gets the content of a note"

  }

},

{

  method: "PUT",

  path: "/note/{slug}",

  handler: (request, h) => {

    return "Edit a note";

  },

  config: {

    description: "Updates the selected note"

  }

},

{

  method: "GET",

  path: "/note/{slug}/delete",

  handler: (request, h) => {

    return "This note no longer exists";

  },

  config: {

    description: "Deletes the selected note"

  }

}

We have followed a similar process like the earlier route definition; however, we’ve modified the method to match the action we want to run.

The main exemption here is the delete route. Here, we will specify it with the GET method instead of the DELETE and include an additional / delete in the push. Hence, we can call the delete action by following this process only by reaching out to the corresponding URL.

Remember: If you decide to implement a robust REST interface, you need to use the DELETE method & remove the /delete part.

We can designate limits in the path by encircling the word with the curly braces. Because we are here to find the notes via a slug, we include {slug} to every path, neglecting the POST route; we don’t need it as we are here not to interact with a particular note, however, to build one.

To know in detail about the Hapi.js routes, you can refer to the official documentation.

Now, here we will integrate our latest routes to the server.js file. Let’s import the routes file at the top of the file:

const Routes = require("./lib/routes");

Then let’s update the existing test route with this code:

server.route(Routes);

Building the Models

Models help us to create a fresh structure of the data and various functions to work with it:

In the next example, we will be utilizing the SQLite database along with the Sequelize.js, which will offer us an efficient interface due to the ORM (Object-Relational Mapping) method. It will also consist of a database-independent interface.

Setting up the Database

You can install SQLite & Sequelize using the code given below:

npm install sequelize sqlite3

Now form a models directory inside the lib/ with a file known as index.js, which will hold the database and Sequelize.js setup, and also consists of the following content:

"use strict";

const Fs = require("fs");

const Path = require("path");

const Sequelize = require("sequelize");

const Settings = require("../../settings");

const dbSettings = Settings[Settings.env].db;

const sequelize = new Sequelize(

  dbSettings.database,

  dbSettings.user,

  dbSettings.password,

  dbSettings

);

const db = {};

Fs.readdirSync(__dirname)

  .filter(file => file.indexOf(".") !== 0 && file !== "index.js")
  .forEach(file => {
    const model = require(Path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
    db[model.name] = model;
  });


db.sequelize = sequelize;

db.Sequelize = Sequelize;

module.exports = db;

In the initial phase, we integrate the models that will be helpful to us:

  • Fs, to check the files present in the models folder, which will consist of all the models
  • Path, to join the path of each file in the existing directory
  • Sequelize, which will help to form a new Sequelize instance
  • Settings, which consists of data of all the settings.js file from the core of our project

Now, we form the latest sequelize variable, which consists of a Sequelize instance along with our database settings for the existing environment. We will be utilizing the sequelize to fetch all the models and make them visible on our db object.

The db object would be exported and consists of database techniques for every model. We can use Node.js with MySQL using Sequelize and Express.js as well.

To include all the models, rather than mentioning them one-by-one, we will check every file present in the files directory (with the exclusion of index.js) and include them via the import function.

Lastly, we include sequelize and Sequelize in our db object. The initial one will be utilized in our server.js file to connect to the database prior to beginning the server, and the second one is integrated for ease if we require it in the later files.

Creating Our Note Model

Here, we would be using the Moment.js package to ease the task of date formatting. To install this package and include it as a dependency, execute the command given below:

npm install moment

We will form a note.js file in the models directory, that will be the only model in the application. It consists of every functionality we require.

Include the code given below in the file:

"use strict";

const Moment = require("moment"); 

module.exports = (sequelize, DataTypes) => {

  const Note = sequelize.define("Note", {

    date: {

      type: DataTypes.DATE,

      get: function() {

        return Moment(this.getDataValue("date")).format("MMMM Do, YYYY");

      }

    },

    title: DataTypes.STRING,

    slug: DataTypes.STRING,

    description: DataTypes.STRING,

    content: DataTypes.STRING

  });

  return Note;

};

We send a function that accepts a sequelize instance to determine the model and DataTypes object, including every type present in our database.

Later, we mention the structure of our data utilizing an object where every key matches the database column and the key’s overall worth determines the kind of data we will be saving.

You can check all the data types in Sequelize.js documentation. The tables in the database are formed instantly according to this information.

Considering the date column, we also mention the method in which the Sequelize must return the value via the getter function (get key).

We describe that prior to returning the information. It must be initially gone via the Moment utility to be formatted in a more readable manner (MMMM Do, YYYY).

Remember: Despite us obtaining a quick and straightforward date string, it’s saved as an accurate date string product of the JavaScript’s Date object. Hence, this is not a destructive operation.

Lately, we are back to our model.

Synchronizing the Database

Here, we will harmonize our database prior to utilizing it for our application. In server.js, import the models above of the file:

// Import the index.js file inside the models directory

const Models = require("./lib/models/");

Then, delete the code snippet given below:

await server.start();

console.log(`Server running at: ${server.info.uri}`);

Add the new code snippet given below:

await Models.sequelize.sync();

await server.start();

console.log(`Server running at: ${server.info.uri}`);

This code will coordinate with the models of the database. As soon as that gets completed, the server will be initiated.

Building the Controllers

Controllers are functions that take the request and the response toolkit objects from the Hapi.js. The request object consists of the details related to the requested resource, and we utilize a reply to provide information to the client.

In our application, we would be getting only the JSON object for now; however, we would include the views as soon as we develop them.

We could consider controllers as functions that connect models with our views; they will interact with the models to fetch data and then provide the data present in the view.

The Home Controller

The primary controller that we will develop will look after the home page of the site. Build a home.js file in the lib/controllers directory, including the code given below:

"use strict";

const Models = require("../models/");

module.exports = async (request, h) => {

  const result = await Models.Note.findAll({

    order: [["date", "DESC"]]

  });

  return {

    data: {

      notes: result

    },

    page: "Home — Notes Board",

    description: "Welcome to my Notes Board"

  };

};

Initially, we obtain complete notes of our database utilizing the findAll method present in our model. This function will be back with a Guarantee, in case it overcomes, we will obtain an array consisting of complete notes in the database.

We can look after the outcomes in a descending way, utilizing the order parameter in the options object thrown away to the findAll method; hence the most recent item would show up first. You can look after all the ready options in the Sequelize.js documentation.

As soon as we obtain the home controller, we will be able to change the routes.js file. Initially, we import the module on the top of the file, after the Path module import:

const Home = require("./controllers/home");

Later we include the controller we have received from the array:

{

  method: "GET",

  path: "/",

  handler: Home,

  config: {

    description: "Gets all the notes available"

  }

},

You have to look after the elements that run smoothly currently by rebooting the server (nodeserver.js) and visiting http://localhost:3000/. You would obtain the code as shown below:

{

  "data": { "notes": [] },

  "page":"Home — Notes Board",

  "description":"Welcome to my Notes Board"

}

Boilerplate of the Note Controller

Given that we would be finding our notes using a slug, we can produce one with the title of the Note and the slug library, so let’s install it and add it as a dependency via the command given below:

npm install slug

The most recent controller that we have mentioned in our application would enable us to create, read, update, and delete notes.

We can move ahead with the node.js file in the lib/controllers directory and include the code snippet given below:

"use strict";

const { Note } = require("../models/");

const Slugify = require("slug");

const Path = require("path");

module.exports = {

  // Here we’re going to include our functions that will handle the remaining requests in the routes.js file.

};

The Create Function

To include a node to our database, here, we will be mentioning a create function inside the create method on our model utilizing the data present in the payload object to include a note in the database.

Include the code snippet given below in the object we are exporting:

create: async (request, h) => {

  const result = await Note.create({

    date: new Date(),

    title: request.payload.noteTitle,

    slug: Slugify(request.payload.noteTitle, { lower: true }),

    description: request.payload.noteDescription,

    content: request.payload.noteContent

  });

  // Generate a new note with the 'result' data

  return result;

},

When the Note is formed, we will look after the note data and share it with the client as JSON utilizing the reply function.

Here, we would only return the result; however, we create the views in the next section, we would build the HTML with the new Note and include it forcefully on the client.

Even though this is not fully imperative and relies on how you will manage your front-end logic, we will get an HTML block to clarify the client’s logic.

Moreover, note that the date is being generated between when we run the function, utilizing the new date().

The read Function

To search only one element, we utilize the findOne method on our model. Because we find notes by their slug, the where filter should consist of the slug offered by the client in the URL (http://localhost:3000/note/:slug:):

read: async (request, h) => {

  const note = await Note.findOne({

    where: {

      slug: request.params.slug

    }

  });

  return note;

},

While in the earlier function, we will only be back with the result, which would be an object consisting of the note information. The views will then be utilized in the Building the Views section.

The Update Function

To update a note, we will utilize the update method on our model. It requires two objects: new values that we would be modifying and the options consisting of the where filter along with the note slug, that’s the exact note we would be modifying:

update: async (request, h) => {

  const values = {

    title: request.payload.noteTitle,

    description: request.payload.noteDescription,

    content: request.payload.noteContent

  };

  const options = {

    where: {

      slug: request.params.slug

    }

  };

  await Note.update(values, options);

  const result = await Note.findOne(options);

  return result;

},

After updating our data, because our database won’t be back with the modified Note, we can identify the updated Note again to go back to the client; therefore, we can display the modified version when the charges are made.

The delete function

The delete controller will eliminate the Note by offering the slug to the destroy function of our model. Later, when the Note is removed, we redirect to the homepage. To achieve this, we utilize the redirect function of Hapi’s response toolkit:

delete: async (request, h) => {

  await Note.destroy({

    where: {

      slug: request.params.slug

    }

  });

  return h.redirect("/");

}

Using the Note Controllers In Routes

Currently, we must consist of a controller file ready along with all the CRUD actions. However, to utilize them, we would have to add it into our routes file:

Initially, let’s import our controller above the routes.js file:

const Note = require("./controllers/note");

We need to update every handler with our new functions, therefore, we must consists of our routes file for Node.js MVC as given below:

{

  method: "POST",

  path: "/note",

  handler: Note.create,

  config: {

    description: "Adds a new note",

    payload: {

      multipart: true,

    }

  }

},

{

  method: "GET",

  path: "/note/{slug}",

  handler: Note.read,

  config: {

    description: "Gets the content of a note"

  }

},

{

  method: "PUT",

  path: "/note/{slug}",

  handler: Note.update,

  config: {

    description: "Updates the selected note",

    payload: {

      multipart: true,

    }

  }

},

{

  method: "GET",

  path: "/note/{slug}/delete",

  handler: Note.delete,

  config: {

    description: "Deletes the selected note"

  }

}

Remember: we’re adding our functions without () at the end, as we’re referencing our functions without calling them.

In Hapi v19, request.payload.multipart was modified to false by default. We require to keep it true for the POST and PUT routes because we will be utilizing a FormData Object to send data to the server, and the passed data would be in the multipart/form-data format.

Building The Views

Currently, our site is getting HTTP calls and replying with JSON objects. To ensure that it is available to all, we need to form pages that better render our information.

In this case, we would be utilizing the Pug (formerly Jade) templating language, even though this isn’t compulsory, and we can utilize different languages with Hapi.js. We will be using the Vision plugin to allow the view functionality in our server.

Remember: If you don’t know the Jade/Pug, view our Beginner’s Guide to Pug.

You can install packages using the code given below:

npm install @hapi/[email protected] pug

Here, we’re installing v5.5.4 of the vision plugin backed up by the Hapi v18. In case you’ve chosen to install Hapi v19, you can just enter npm i @hapi/vision to drag the recent version.

The Note Component

Initially, we will create a note component which will be used repetitively in our views. Moreover, we would be using this component in several of our component functions to build a note on the fly in the back end to modify the client’s logic.

Build a file in lib/views/components known as note.pug including code given below:

article.content

  h2.title: a(href=`/note/${note.slug}`)= note.title

  p.subtitle.is-6 Published on #{note.date}

  p=note.content

It consists of the note title, publication date, and overall data present on the Note.

The Base Layout

The base layout has the most basic elements of our pages – or in simple words, for instance, all the things that are not included in the content. Make a file in /lib/views known as layout.pug along with the code given below:

doctype html

head

  meta(charset='utf-8')

  meta(name='viewport' content='width=device-width, initial-scale=1')

  title=page

  meta(name='description' content=description)

  link(rel='stylesheet' href='https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css')

  script(defer='' src='https://use.fontawesome.com/releases/v5.3.1/js/all.js')

body

  block content

  script(src='/scripts/main.js')

The content of other pages would be installed in place of block content. Moreover, note that we would show a page variable in the title element, and a description variable meta(name= ‘description’) element.  We will make & include the variables in the routes later.

For styling purposes, we would be using the Bulma CSS framework and Font Awesome from a CDN. We would be adding a main.js file below the page, that will consist of our personalized JS code for the front-end. Also, make the file inside the static/public/scripts directory.

The Home View

On the home page, we will present every Note in the database and a button that will display a modal window and a form that enables us to make a new note via Ajax.

Setup a file in lib/views known as home.pug, along with the content given below:

extends layout

block content

  section.section

    .container

      h1.title.has-text-centered

        | Notes Board

      .tabs.is-centered

        ul

          li

            a.show-modal(href='#') Publish

      main(container).notes-list

        each note in data.notes

          include components/note

          hr

      .modal

        .modal-background

        .modal-card

          header.modal-card-head

            p.modal-card-title Add note

            button.delete(aria-label='close')

          section.modal-card-body

            form(action='/note' method='POST').note-form#note-form

              .field

                .control

                  input.input(name='noteTitle' type='text' placeholder='Title')

              .field

                .control

                  input.input(name='noteDescription' type='text' placeholder='Short description')

              .field

                .control

                  textarea.textarea(name='noteContent' placeholder='Contents')

              .field

                .control

                  button.button.is-link Save

The Note View

The note page is quite the same as the home page; however, we can display a menu, including options particular to the existing Note.

The Note’s content consists of the same form just like the home page; however, with the existing note information is present, it’s there as soon as we update it.

Make a file in lib/views known as note.pug, including the code snippet given below:

extends layout

block content

  section.section

    .container

      h1.title.has-text-centered

          | Notes Board

      .tabs.is-centered

        ul

          li: a(href='/') Home

          li: a.show-modal(href='#') Update

          li: a(href=`/note/${note.slug}/delete`) Delete

      include components/note

      .modal

        .modal-background

        .modal-card

          header.modal-card-head

            p.modal-card-title Edit note

            button.delete(aria-label='close')

          section.modal-card-body

            form(action=`/note/${note.slug}` method='PUT').note-form#note-form

              .field

                .control

                  input.input(name='noteTitle' type='text' placeholder='Title' value=note.title)

              .field

                .control

                  input.input(name='noteDescription' type='text' placeholder='Short description' value=note.description)

              .field

                .control

                  textarea.textarea(name='noteContent' placeholder='Contents') #{note.content}

              .field

                .control

                  button.button.is-link Save

The JavaScript on the Client

To form and update notes, we will take the help of JavaScript for two main things: to display modal with the form and provide the request through Ajax. Even though this is not highly important, we feel it offers a top-notch experience to the user.

Here is the content for our main.js file in the static/public/scripts/ directory:

/ Modal

const modal = document.querySelector(".modal");

const html = document.querySelector("html");

const showModal = () => {

  modal.classList.add("is-active");

  html.classList.add("is-clipped");

};

const hideModal = () => {

  modal.classList.remove("is-active");

  html.classList.remove("is-clipped");

};

document.querySelector("a.show-modal").addEventListener("click", function(e) {

  e.preventDefault();

  showModal();

});

modal.querySelector(".modal .delete").addEventListener("click", function(e) {

  e.preventDefault();

  hideModal();

});

// Form submition

const form = document.querySelector("#note-form");

const url = form.getAttribute("action");

const method = form.getAttribute("method");

const prependNote = html => {

  const notesList = document.querySelector(".notes-list");

  const div = document.createElement("div");

  div.innerHTML = html;

  notesList.insertBefore(div.firstChild, notesList.firstChild);

};

const updateNote = html => {

  const article = document.querySelector("article");

  const div = document.createElement("div");

  div.innerHTML = html;

  article.parentNode.replaceChild(div.firstChild, article);

};

const onSuccess = html => {

  hideModal();

  form.reset();

  if (method === "POST") {

    prependNote(html);

  } else if (method === "PUT") {

    updateNote(html);

  }

};

form.addEventListener("submit", e => {

  e.preventDefault();

  fetch(url, {

    method,

    body: new FormData(form)

  })

    .then(response => response.text())

    .then(text => onSuccess(text))

    .catch(error => console.error(error));

});

Each time the user fills up the form in the modal window, we obtain information about form elements and transfers to the back end, based on the action URL and the method (POST or PUT).

Later, we obtain outcome as a block of the HTML consisting of our new note data. As soon as we include a note, we include it above the home page, and as soon as we modify a note, we replace the content for the latest one in the note view.

Adding Support for Views on the Server

To utilize our views, we need to integrate them into our controllers and consider the required settings. It will help in Node.js MVC app creation.

Inside the server.js file, let’s integrate the Node Path utilizing at the top of the file, as we are utilizing it in our code to show the path of our views:

const Path = require("path");

Now, change the server.route(Routes); line with  the code snippet given below:

await server.register([require("@hapi/vision")]);

server.views({

  engines: { pug: require("pug") },

  path: Path.join(__dirname, "lib/views"),

  compileOptions: {

    pretty: false

  },

  isCached: Settings.env === "production"

}) 

// Add routes

server.route(Routes);

As the above code suggests; initially, we register the Vision plugin with our Hapi.js server, which will offer view functionality. After this, we mention the settings for our views, such as the engine we would be using and the route where the views are situated.

In the end of the code block, we mention our routes.

This indicates that our views will work on the server; however, we still have to mention the view that we will be using for every route.

Setting the Home View

Open the lib/controllers/home.js file and change the return statement with the snippet given below:

return h.view('home', {

  data: {

    notes: result

  },

  page: 'Home — Notes Board',

  description: 'Welcome to my Notes Board'

});

On registering the Vision plugin, we now consist of a view method present on the reply object. We would be using it to choose the home view inside the views directory and to send the data that we will utilize for rendering the views.

Along with the data that we include in the view, we can also mention the meta title and meta description as it is useful for the search engines.

In case you want to try things out at this point, visit http://localhost:3000/. You must observe a properly styled notes board, along with a Publish button which don’t affect at all.

Setting the Note View: create Function

Currently, each time we make a note, we dispatch a JSON object from the server to the client. However, as we would be combining this process with Ajax, we can transmit the latest Note as HTML ready to be included on the page. To achieve this, using our data, we render the out note component.

Begin with requiring Pug above the controllers/note.js file:

const Pug = require("pug");

Later, in the create method, modify the line return result; with the code snippet given below:

// Generate a new note with the 'result' data

return Pug.renderFile(

  Path.join(__dirname, "../views/components/note.pug"),

  {

    note: result

  }

);

We utilize the renderFile method from Pug to render the note template along with all the details we obtained from our model.

Setting the Note View: read Function

As soon as we access the note page, we must obtain the note template with the details of our Note. Here we need to replace the read function’s return note; line by code snippet as follows:

return h.view("note", {

  note,

  page: `${note.title} — Notes Board`,

  description: note.description

});

Similar to the home page, we choose view as our first variable and data that we will be using as a later variable.

Setting the Note View: update Function

Each time we modify a note, we reply in the same manner when we make new notes. Modify the return result; line with the update function code as given below:

// Generate a new note with the updated data

return Pug.renderFile(

  Path.join(__dirname, "../views/components/note.pug"),

  {

    note: result

  }

);

Read also: Diving Into Node JS 14 Features In Detail

Serving Static Files

The Hapi.js offer the JS and CSS files that we will be utilizing on the client-side from the static/public/ directory. However, this doesn’t happen instantly;

We need to mention to the server that we want to specify this folder as public. This is achieved with the Inert package’s help, which you need to install via the command given below:

npm install @hapi/inert

Here, in the server.register function inside the server.js file, import the Inert plugin and register it with Hapi as given below:

await server.register([require("@hapi/vision"), require("@hapi/inert")]);

Here, we need to mention the path that we are going to offer the static files and their location on the server’s filesystem. Include the entry given below in the last of the exported objects in routes.js.

{

  // Static files

  method: "GET",

  path: "/{param*}",

  handler: {

    directory: {

      path: Path.join(__dirname, "../static/public")

    }

  },

  config: {

    description: "Provides static resources"

  }

}

This path will utilize the GET method, and we have substituted the handler function along with an object consisting of the directory that we want to make public.

You can discover details about serving the static content in the Hapi.js documentation.

Conclusion

We hope that after going through this blog, you have understood the importance of combining Node JS MVC applications.

Apart from this, we have also built a basic Hapi.js application with the help of the MVC pattern. To take your Node JS MVC app to the next level, you can hire NodeJS developers who recommend you integrate an authentication system into your application.

Chintan Gor

CTO, eSparkBiz

Enthusiastic for web app development, Chintan Gor has zeal in experimenting with his knowledge of Node, Angular & React in various aspects of development. He keeps on updating his technical know-how thus pinning his name among the topmost CTO's in India. His contribution is penned down by him through various technical blogs on trending tech. He is associated with eSparkBiz from the past 14+ years where one can get premium services.
Frequently Asked Questions
  1. Does Node.js Use MVC?

    Not all node.js frameworks rely on the MVC design pattern. However, using this pattern would make it easy for you to develop complex apps

  2. What Is app.use In Node JS?

    app.use is Application Level Middleware in the Node JS Framework.

  3. What Is MVC Architecture Pattern?

    MVC is an architectural pattern that separates an application into three main logical components: the model, the view, and the controller.

  4. Is React A MVC Framework?

    The simple answer to this question would be NO. React is not an MVC framework.

  5. Is MVC Still Relevant In 2024?

    The simple answer to this question would be YES. MV is still relevant in 2024 and will continue to do so in the future as well.

  6. What Are The Benefits Of MVC Architecture Pattern?
    • Faster development process
    • Provide multiple views
    • Support for asynchronous technique
    • Returns the data without formatting
    • SEO friendly

Expert Insights For Digital Product Development

We at eSparkBiz are passionate about discussing recent technologies and applications. We constantly write blogs and articles associated with the field of technology. So, don't miss our detailed and insightful write-ups. You'll find all our latest blogs and blog updates here.

Node.js Microservices: Build Scalable and Efficient Applications

eSparkbiz Technologies Pvt Ltd
Chintan Gor CTO, eSparkBiz

Once your applications begin to grow and surpass a certain threshold, it is time you settle on an architecture that will make adding new features…

Top Node.js Interview Questions to Ask Before Hiring a Node.js Developer

eSparkbiz Technologies Pvt Ltd
Chintan Gor CTO, eSparkBiz

NodeJS emerged recently in the tech landscape as an effective JavaScript framework to develop lightning-speed server apps. With companies racing to design robust digital product…

The Ultimate Guide to Node.js Development Services for Scalable Web Solutions

eSparkbiz Technologies Pvt Ltd
Chintan Gor CTO, eSparkBiz

NodeJS has been ranked as the number one framework leveraged by Node.JS developers around the globe. Nearly 40.8% of skilled developers worldwide make use of…