How to Build and Structure a Node.js MVC Application

.gitignore 3D accessibility accessible headings ad banner ad revenue ad revenues adobe XD Advanced advanced-css-tools advanced-css-tutorials AdvancedCSS AdvancedCSS-hub agency Agile Agile Development ai Ajax Alex Skill Kit Alexa Alexa skill AlexW Amazon Alexa amazon developer analytics Angelap angular AngularJS Animation anime.js ANN api apollo Appsflyer architecture ARIA aria attributes array methods artificial neural network Async async components AurelioD automation Avocode AWS AWS Lambda axios b2b b2c baas background background eraser tool Banner banner ad Bannersnack bash bash commands bawmedia bem bitcoin black friday Blogs bootstrap bot browser testing Browsers build process bundler Business business success C Canvas & SVG career Career Advancement CDN Chakra UI charting chat app chatbots ChatKit chrome chrome developer tools chrome extension chrome extensions chromium ciphering cli cli tools climate climate action clip-path clojure cloning Cloud cloud hosting CloudFlare Cloudways cms CNN code challenge ColinI collaboration collaboration tools color contrast committing changes compiler components conflicts const assertions constructs content content delivery network content organization context api convoluted neural network create react app cron job cross browser testing cross platform development cross-browser cryptocurrencies cryptocurrency CSS CSS Animations CSS Architecture css blur css challenge css framework css frameworks css grid css layout css master css modules CSS print css rem css typography CSS-in-JS CSS3 css3 transforms cssbattle.dev D3.js data science data visualization database databases debugging decoupled cms deep linking delay Design Design & UX design career design handoff Detox dev environment developer job developer tools DevOps devtools diagnostic analytics digit recognition digital agency digitalocean Discord discounts django Docker Docker Containers drag & drop dropzone.js E-commerce ECMAScript edge computing editor elastic em units employment encryption end-to-end testing energy energy usage Entrepreneur Entrepreneurship environment erasing backgrounds es6 ES6 classes eslint Expo Express FaaS face detection face recognition fast wordpress themes Feathers Feathers.js figma flask flatfile flexible wordpress theme forking form validation forms tutorial framework Frameworks freelancing functional programming functions gatsby generators geolocation get organized getting a job GIPHY git git branching git lfs git merging gitCS github github API Google Photos GraphCMS graphing graphql Growth Grunt grunt-watch Gulp hapi haskell headless CMS hide and show page elements hooks hosting HTML HTML & CSS html preprocessor HTML5 HTML5 Dev Center HTML5 Tutorials & Articles https://www.sitepoint.com/windows-linux-subsystem-2-windows-terminal IAAS IDE Illustration image optimization immutability Interview ionic IP2Location ipdata jade jamesh JavaScript javascript framework javascript frameworks javascript tooling Jest job job hunting job interview joelf jQuery jQuery functions jquery get url params jquery string functions jQuery.each() jQuery.each() Array Example jquery4u js js framework JS tooling Keras keycdn kubernetes lambdatest launch checklist Layout learn javascript learn typescript learn-advanced-css learn-modernjs Learn-Node-JS learn-vue Let's Encrypt Lift linux linux containers location tracking LouisL machine learning Managed managed hosting managing remotes mariap marvel MATLAB MBaaS mekanism mental health merging mind map mind mapping mindmapping Mobile mobile attribution Mobile Design modern css modernjs modernjs-hub modernjs-tools modernjs-tutorials modules moment.js monday mongodb MTCNN multi-threading mvc mysql MySQL install natural language processing Navigation navigation library nest Next.js nginx nilsonj no-code node node version manager node-hub Node-JS-Tools Node-JS-Tutorials node-mysql module Node.js NodeGui nodejs noops nosql npm nvm objects octobercms Offers on-premise online publishing organize organize files PaaS Package Management package manager pair programming pairing patrickc pause Percy Performance Photography & Imagery Photoshop Photoshop Tutorials & Articles pip pipeline portfolio portfolio builders preprocessors prettier print styles Prisma Private Class Fields product design Productivity productivity tips Profitability Programming programming job PubNub Pug Puppeteer Pusher python python framework python interview python job Quasar query strings quick-tip quiz quote builders r Raw JavaScript React react components React Hooks React Hooks Form React native React-Tools reactive Reactive Programming reactive web apps read offline Reat native elements Recurring Payments recursive types RedBox redux rem units remote job remote work remote work tips remote working removing backgrounds resilient Resources responsive REST restful api Review SaaS sampling sass Scss self sencha Sequelize server server-side serverless serverless computing shared hosting sharing state sharpen shell shell commands side project siteground sketch slashdata sleep Software software commits sorting sourcetree specifications speech recognition speech recognition api sponsored sql ssl Stack Overflow Startups state state management static module bundler StudioWorks Styled Components styling React components survey sustainability svelte sympli Tailwind task runner telegram bots Template engine Testing text to speech Themes Tic Tac Toe Tinder toggle Tolerance tooling tooling anxiety tools Tools & Libraries transformations TypeScript typescript skills Ubuntu UI UI Design Usability user experience user research UX ux issues vanilla javascript VGGFace2 virtual machine visual testing voice recognition vps vue Vue CLI vue dev environment vue-hub vue-tutorials vue.js vuetify vuex wait WatermelonDB Web Web Design web hosting Web Hosting & Domains Web standards web workers webpack website builders website performance WebSockets windows windows 10 home Windows Subsystem for Linux Windows Terminal wix women women in tech WordPress wordpress plugin wordpress themes WordPress theming work workers workflow working remotely workplaces WPEngine WSL WSL2 YellowBox Zeplin zsh zsh command zsh plugin zsh tools

Inside the monitor a puppet manipulates on-screen windows and popups

In a non-trivial application, the architecture is as important as the quality of the code itself. We can have well-written pieces of code, but if we don’t have good organization, we’ll have a hard time as the complexity increases. There’s no need to wait until the project is half-way done to start thinking about the architecture; the best time is before starting, using our goals as beacons for our choices.

Node.js doesn’t have a de facto framework with strong opinions on architecture and code organization in the same way that Ruby has the Rails framework, for example. As such, it can be difficult to get started with building full web applications with Node.

In this tutorial, we’re going to build the basic functionality of a note-taking app using the MVC architecture. To accomplish this, we’re going to employ the Hapi.js framework for Node.js and SQLite as a database, using Sequelize.js, plus other small utilities, to speed up our development. We’re going to build the views using Pug, the templating language.

What is MVC?

Model-View-Controller (or MVC) is probably one of the most popular architectures for applications. As with a lot of other cool things in computer history, the MVC model was conceived at PARC for the Smalltalk language as a solution to the problem of organizing applications with graphical user interfaces. It was created for desktop applications, but since then, the idea has been adapted to other mediums including the Web.

We can describe the MVC architecture in simple terms:

  • Model: the part of our application that will deal with the database or any data-related functionality.
  • View: everything the user will see — basically, the pages that we’re going to send to the client.
  • Controller: the logic of our site, and the glue between models and views. Here we call our models to get the data, then we put that data on our views to be sent to the users.

Our application will allow us to create, view, edit and delete plain-text notes. It won’t have other functionality, but because we’ll have a solid architecture already defined we won’t have a lot of trouble adding things later.

This tutorial assumes you have a recent version of Node installed on your machine. If this isn’t the case, please consult our tutorial on getting up and running with Node.

You can check out the final application in the accompanying GitHub repository, so you get a general overview of the application structure.

Laying out the Foundation

The first step when building any Node.js application is to create a package.json file, which is going to contain all of our dependencies and scripts. Instead of creating this file manually, npm can do the job for us using the init command:

mkdir notes-board
cd notes-board
npm init -y

After the process is complete, we’ll have a package.json file ready to use.

Note: if you’re not familiar with these commands, checkout our Beginner’s Guide to npm.

We’re going to proceed to install Hapi.js — the framework of choice for this tutorial. It provides a good balance between simplicity, stability and features that will work well for our use case (although there are other options that would also work just fine).

npm install @hapi/hapi@18.4.0

This command will download Hapi.js and add it to our package.json file as a dependency.

Note: We’ve specified v18.4.0 of Hapi.js, as it’s compatible with Node versions 8, 10, and 12. If you’re using Node 12, you can opt to install the latest version (Hapi v19.1.0).

Now we can create our entry file — the web server that will start everything. Go ahead and create a server.js file in your application directory and add the following 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();

This is going to be the foundation of our application.

First, we indicate that we’re going to use strict mode, which is a common practice when using the Hapi.js framework.

Next, we include our dependencies and instantiate a new server object where we set the connection port to 3000 (the port can be any number above 1023 and below 65535).

Our first route for our server will work as a test to see if everything is working, so a “Hello, world!” message is enough for us. In each route, we have to define the HTTP method and path (URL) that it will respond to, and a handler, which is a function that will process the HTTP request. The handler function can take two arguments: request and h. The first one contains information about the HTTP call, and the second will provide us with methods to handle our response to that call.

Finally, we start our server with the server.start() method.

Storing Our Settings

It’s good practice to store our configuration variables in a dedicated file. This file exports a JSON object containing our data, where each key is assigned from an environment variable — but without forgetting a fallback value.

In this file, we can also have different settings depending on our environment (such as development or production). For example, we can have an in-memory instance of SQLite for development purposes, but a real SQLite database file on production.

Selecting the settings depending on the current environment is quite simple. Since we also have an env variable in our file which will contain either development or production, we can do something like the following to get the database settings:

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

So dbSettings will contain the setting of an in-memory database when the env variable is development, or will contain the path of a database file when the env variable is production.

Also, we can add support for a .env file, where we can store our environment variables locally for development purposes. This is accomplished using a package like dotenv for Node.js, which will read a .env file from the root of our project and automatically add the found values to the environment.

Note: if you decide to also use a .env file, make sure you install the package with npm install dotenv and add it to .gitignore so you don’t publish any sensitive information.

Our settings.js file will look like this:

// 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 start our application by executing the following command and navigating to http://localhost:3000 in our web browser:

node server.js

Note: this project was tested on Node v12.15.0. If you get any errors, ensure you have an updated installation.

Defining the Routes

The definition of routes gives us an overview of the functionality supported by our application. To create our additional routes, we just have to replicate the structure of the route that we already have in our server.js file, changing the content of each one.

Let’s start by creating a new directory called lib in our project. Here we’re going to include all the JS components.

Inside lib, let’s create a routes.js file and add the following content:

"use strict";
const Path = require("path");

module.exports = [
  // we’re going to define our routes here
];

In this file, we’ll export an array of objects that contain each route of our application. To define the first route, add 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 first route is for the home page (/), and since it will only return information, we assign it a GET method. For now, it will only give us the message “All the notes will appear here”, which we’re going to change later for a controller function. The description field in the config section is only for documentation purposes.

Then, we create the four routes for our notes under the /note/ path. Since we’re building a CRUD application, we’ll need one route for each action with the corresponding HTTP methods.

Add the following definitions next to the previous 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’ve done the same as in the previous route definition, but this time we’ve changed the method to match the action we want to execute.

The only exception is the delete route. In this case, we’re going to define it with the GET method rather than DELETE and add an extra /delete in the path. This way, we can call the delete action just by visiting the corresponding URL.

Note: if you plan to implement a strict REST interface, then you would have to use the DELETE method and remove the /delete part of the path.

We can name parameters in the path by surrounding the word in curly braces. Since we’re going to identify notes by a slug, we add {slug} to each path, with the exception of the POST route; we don’t need it there because we’re not going to interact with a specific note, but to create one.

You can read more about Hapi.js routes on the official documentation.

Now, we have to add our new 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 replace our current test route with the following:

server.route(Routes);

The post How to Build and Structure a Node.js MVC Application appeared first on SitePoint.

Original Article

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.