How to create a website using Node.js and Express

Updated: 03/10/2024 by Computer Hope
Node.js web application

This example creates a website using Node.js to provide logical website behavior. Using the Express.js framework, the website is implemented as a web application, with logical routing to other sections of the website.

The HTML (hypertext markup language) and CSS (cascading style sheets) is based on our responsive website design using CSS Grid and Flexbox. The HTML is refactored as a template, so layout code can be reused when adding new pages.

Install Node

Node.js, also called Node, is a runtime environment for writing server-side applications in JavaScript.

Note

If Node is already installed on your computer, you can skip this section and proceed to make a new Express app.

Download the Node installer from the official Node.js downloads website. Choose the LTS (long term support) version for your operating system.

Windows and macOS

Open and run the Node installer (.msi on Windows, .pkg on macOS).

On Windows, at the installation screen labeled Tools for Native Modules, check the box Automatically install the necessary tools.

Linux

On Linux systems, you can install Node using your package manager, install the compiled binaries manually, or build Node from source. For detailed information, refer to the official Node.js installation wiki.

All operating systems

When installation is complete, open a terminal or command prompt window. Run the following command to update npm, the Node package manager. The -g (global) switch specifies that the software is installed system-wide, not only the current Node app.

Windows

npm install -g npm

Linux and macOS

sudo npm install -g npm

Finally, use npm to globally install the express-generator application.

Windows

npm install -g express-generator

Linux and macOS

sudo npm install -g express-generator

Make a new Express app

In a terminal or command prompt window, generate a new Express.js app. In our example, the app name is myapp, and the view engine is specified as pug.

express myapp --view="pug"

Change directory to the new Express app.

cd myapp

In the Express app directory, use npm install to download and install the required dependencies, as listed in the package.json file.

npm install

If any security updates are available for the installed dependencies, a notification is displayed.

found 1 low severity vulnerability
  run `npm audit fix` to fix them, or `npm audit` for details

If so, apply the security updates.

npm audit fix

Install nodemon

In the Express app directory, install nodemon. The option --save-dev indicates that nodemon is a development dependency. It is not used in the application itself, but is a tool used during development.

npm install --save-dev nodemon

Add a development startup script

A development startup script provides a way to start your web application with options that help you develop the app, such as verbose error messages.

In a text editor, open the file package.json in the app directory. This JSON (JavaScript Object Notation) file specifies the dependencies used by your Node app. Additionally, it contains named startup scripts that start the application in different ways.

In package.json, locate the "scripts" entry. By default, it contains only one script ("start").

  "scripts": {
    "start": "node ./bin/www"
  },

Add a new line that defines a script devstart as follows.

Linux and macOS

  "scripts": {
    "start": "node ./bin/www",
    "devstart": "DEBUG=myapp:* nodemon ./bin/www"
  },

Windows

  "scripts": {
    "start": "node ./bin/www",
    "devstart": "SET DEBUG=myapp:* & nodemon ./bin/www"
  },

These scripts ("start" and "devstart") can be executed by running the command npm run scriptname.

The command npm run devstart starts the app with two additional development features enabled.

  • The DEBUG environment variable is set, specifying that the console log and error pages, such as HTTP (hypertext transfer protocol) 404, display additional information, like a stack trace.
  • In addition, nodemon monitors certain important website files. If you modify these files, such as redesigning a page or modifying static content, nodemon automatically restarts the server to reflect the changes.

Start the web server in development mode.

npm run devstart
Tip

If the Windows Firewall blocks the web server application, click Allow Access.

Preview the web app

When the application is running, your computer acts as a web server, serving HTTP on port 3000.

To preview the website, open a web browser to the address localhost:3000.

Default Express.js app

Any device connected to your local network can view the application at address ipaddress:3000, where ipaddress is the local IP address of the computer running the app.

Tip

If you're unsure what the computer's local IP address is, see: How to find my IP address.

To preview the website on a mobile device, connect its Wi-Fi to your local network, and open the address in a browser.

Express.js on mobile

HTML templates

Our example uses the CSS, JavaScript, and HTML from the how to create a responsive website using CSS Grid and Flexbox. The CSS and JavaScript are used verbatim. The HTML is refactored to a templating language.

Using a templating language, the layout code is written only once, and inherited by other pages.

The software that converts a template to its final format is called a template processor. In the context of HTML, a template processor is called a view engine.

Express.js supports several view engines, including Pug.

Overview of Pug

The Pug language describes HTML documents, in a way that provides benefits and additional features. Pug files are rendered to HTML when the user requests them.

Pug's language syntax removes the need for tags to be closed, or enclosed in brackets. It also supports inherited templates, iteration, conditionals, and JavaScript evaluation.

Example HTML to Pug conversion

These are the first few lines of the HTML from the how to create a responsive website using CSS Grid and Flexbox.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta charset="utf-8">
    <title>Title</title>
    <link rel="stylesheet" href="index.css">
    <script src="index.js"></script>
  </head>
  <body>
    <div id="menu">
      <section id="menuitems">
        <div class="menubutton">
          <h1 onclick="menuToggle('hide')" class="menubutton">&#9776;</h1>

In Pug, the same HTML can be written like this.

doctype html
html
  head
    meta(name="viewport" content="width=device-width, initial-scale=1")
    meta(charset="utf-8")
 title Title
    link(rel="stylesheet", href="index.css")
    script(src="index.js")
  body
    #menu
      section#menuitems
        .menubutton
          h1.menubutton(onclick="menuToggle('hide')") &#9776;

Element tags are written without brackets. Child elements are indented. The level of indentation determines the scope of an element, so closing tags are not necessary.

The "id" and "class" CSS selectors can be written as element#id, element.class, element#id.class, etc. If no element is specified, the element is assumed to be a div. For example, <div class="foo"> in HTML can be written as .foo in Pug.

After the element name and its selectors, attributes can be specified in parentheses, as a comma-delimited list. For example:

HTML

<div class="button" onmouseover="glow()" onclick="start()">

Pug

.button(onmouseover="glow()", onclick="start()")

Listing multiple elements in one line

If the element is followed by a colon (:), it can be followed by a child element on the same line. The following two sections of Pug produce the same HTML output.

a(href="/home")
  p Home
a(href="/home"): p Home

Both of the above are rendered to the following HTML.

<a href="/home"><p>Home</p></a>

Evaluating JavaScript

If the element is followed by an equals sign (=), everything that follows on that line is interpreted as buffered code. The code is evaluated as JavaScript, and the output is "buffered" (included as the element's content). In its simplest form, buffered code can be the name of a variable, passed by the application.

For example, the app router for the home page, index.js, passes the variable title with the value "Our Farm Stand" to the method express.Router(), which passes it to Pug. When Pug renders layout.pug, the following line:

title= pagetitle

...is interpreted as:

title Our Farm Stand

...which is rendered as the following HTML:

<title>Our Farm Stand</title>

Template inheritance

Pug documents can inherit other Pug documents using the keywords extends and block.

For example, you can create a basic website layout, layout.pug, with shared elements of the page.

doctype html
html
  head
 title Page Title
  body
    p Content
    block foo

The block foo statement says "insert a block of content here, named foo, specified in another Pug document that inherits this template."

Documents that inherit layout.pug must begin with the statement extends layout, and contain a block foo statement at the top indentation level (at the beginning of a new line). The children of this "block foo" statement are inserted in the template at the location of the corresponding block.

A Pug document can inherit layout.pug like the following.

extends layout
block foo
  p This is the home page.

When the document is rendered, the Pug engine loads the file layout.pug. The line block foo in layout.pug is replaced with p This is the home page.

Overview of the default Express app

The default structure of the Express app is listed here, with descriptions of each file and directory.

myapp/                  (Contains the entire Express app)
├─ app.js               The core logic of the Express app.
├─ bin/                 (Contains the app's executable scripts)
│  └─ www               A wrapper that runs app.js.
├─ node_modules/        (Contains dependencies installed by npm)
├─ package-lock.json    JSON manifest of installed dependencies.
├─ package.json         JSON of dependencies and config specific to your app.
├─ public/              (Files downloaded by the user's web browser)
│  ├─ images/           (Contains client-accessible image files)
│  ├─ javascripts/      (Contains client-accessible JavaScript files)
│  └─ stylesheets/      (Contains client-accessible CSS)
│     └─ style.css      The site's CSS stylesheet.
├─ routes/              (Contains logic for individual site routes)
│  ├─ index.js          Logic of the "index" route (/).
│  └─ users.js          Logic of the "users" route (/users).
└─ views/               (Contains HTML templates)
   ├─ error.pug         View displayed for error pages, such as HTML 404.
   ├─ index.pug         View displayed for the site root (/).
   └─ layout.pug        View template of layout shared by all pages.

Core functionality of the website is defined in app.js. Routes are named and specified in this file.

A route is a page or section of the site with a unique path in the URL (uniform resource locator), such as www.example.com/search, www.example.com/login, etc. These routes are named, and associated with route logic scripts, in app.js.

Route logic scripts are stored in the routes folder. When a user requests a route, its route logic script processes the HTTP request data and sends a response.

The views folder contains the HTML templates, called views, which are processed by the view engine (Pug).

Implementation: JavaScript, CSS, and Pug

The following code implements the Express web app.

App file structure

myapp/
├─ app.js               App core logic
├─ bin/
│  └─ www
├─ node_modules/
├─ package-lock.json
├─ package.json
├─ public/
│  ├─ images/
│  ├─ javascripts/
│  │  └─ menu.js        Implements menu toggle
│  └─ stylesheets/
│     └─ style.css      Stylesheet
├─ routes/
│  ├─ about.js          Logic for route /about
│  ├─ advice.js         Logic for route /advice
│  ├─ contact.js        Logic for route /contact
│  ├─ index.js          Logic for route /
│  ├─ recipes.js        Logic for route /recipes
│  ├─ tips.js           Logic for route /tips
│  └─ users.js          Not used, can be deleted
└─ views/
   ├─ about.pug         View for route /about
   ├─ advice.pug        View for route /advice
   ├─ contact.pug       View for route /contact
   ├─ error.pug
   ├─ index.pug         View for route /
   ├─ layout.pug        View template shared by all pages
   ├─ recipes.pug       View for route /recipes
   └─ tips.pug          View for route /tips
blue = modified, green = new, red = not used

myapp/app.js

The core app logic is essentially the same as the default Express app, with additional routes defined. The "users" route is removed.

// core dependencies
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
// create route objectsvar indexRouter = require('./routes/index');var aboutRouter = require('./routes/about');var contactRouter = require('./routes/contact');var tipsRouter = require('./routes/tips');var recipesRouter = require('./routes/recipes');var adviceRouter = require('./routes/advice');
// the app object
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
// app config
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// tell the app to use these routesapp.use('/', indexRouter);app.use('/about', aboutRouter);app.use('/contact', contactRouter);app.use('/tips', tipsRouter);app.use('/recipes', recipesRouter);app.use('/advice', adviceRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};
  // render the error page
  res.status(err.status || 500);
  res.render('error');
});
// expose this app to scripts that require it, i.e. myapp/bin/www
module.exports = app;

myapp/routes/layout.pug

The layout.pug file contains the core layout of the page, which is shared by every page on the site. It contains everything required to display a page, except for the mainbody content (block mainbody).

doctype html
html
  head
 title= pagetitle
    meta(charset="utf-8")
    meta(name="viewport" content="width=device-width, initial-scale=1")
    script(src="/javascripts/menu.js")
    link(rel="stylesheet", href="/stylesheets/style.css")
  body
    #menu
      section#menuitems
        .menubutton
          h1.menubutton(onclick="menuToggle('hide')") &#9776;
        a(href="/")
          h3.menuhead Our Farm Stand
        a(href="/tips")
          h3.sectrule Tips for living well
        a(href="/recipes")
          h3 Recipes
        a(href="/advice")
          h3 Homesteading advice
        a(href="/about")
          h3.sectrule About Us
        a(href="/contact")
          h3 Contact Us
    #container
      #header
        a(href="/")
          h1.logo Our Farm Stand
        .headspace
        h1.menubutton(onclick="menuToggle('show')") &#9776;
        h1.placeholder &#9776;
        h2.navitem
          a(href="/about")
            .clickable-area About Us
        h2.navitem
          a(href="/contact")
            .clickable-area Contact Us
      #panel.left
        section#sections
          .sectionlink
            a(href="/tips")
              .clickable-area Tips for living well
          .sectionlink
            a(href="/recipes")
              .clickable-area Recipes
          .sectionlink
            a(href="/advice")
              .clickable-area Homesteading advice
      block mainbody
      #panel.right
        h3 Our friends
        section#partners.tall
          .partnerlink
            a(href="/")
              .clickable-area Green Valley Greens
          .partnerlink
            a(href="/")
              .clickable-area Turkey Hill Farm
          .partnerlink
            a(href="/")
              .clickable-area Burt's Maple Syrup
          .partnerlink
            a(href="/")
              .clickable-area Only Organic Seeds
      #footer
        p Copyright &copy; 2020 Alice &amp; Bob's Farm Stand

myapp/views/index.pug

The index.pug file extends layout.pug, and contains mainbody content for the route /.

extends layout
block mainbody
  #mainbody
    section.mainbodyitems
      h3 Announcements
      section.announcements
        .announceitem
          h4.title Open for business
          p.date Jan. 15
          p Renovations of our new storefront are complete, and we're open for business.
      h3 Items for sale
      section.forsaleitems
        table
          tr
            th Item
            th Description
            th Price
            th.qty Qty
          tr
            td Milk
            td Good source of calcium.
            td.price $2
              span.perunit  / half gal.
            td.qty 3
          tr
            td Eggs
            td Great for breakfast and baking.
            td.price $4
              span.perunit  / doz.
            td.qty 6
          tr
            td Whole chicken
            td Perfect for roasting.
            td.price $5
              span.perunit  / lb.
            td.qty 4
      h3 Upcoming events
      section
        .eventitem
          h4.title Cider Fest
          p.date October 20, 2pm&ndash;6pm
          p Celebrate the season with fresh-pressed cider from our orchards.
        .eventitem
          h4.title Bread baking workshop
          p.date December 13, 9am&ndash;noon
          p Learn how to create and cultivate a sourdough starter.
      h3 Message of the day
      section
        .motditem
          p Eat better food. Support your local farm stand.
        h3#partners.wide Our friends
        section#partners.wide
          .partnerlink.wide
            a(href="")
              .clickable-area Green Valley Greens
          .partnerlink.wide
            a(href="")
              .clickable-area Turkey Hill Farm
          .partnerlink.wide
            a(href="/")
              .clickable-area Burt's Maple Syrup
          .partnerlink.wide
            a(href="")
              .clickable-area Only Organic Seeds
        .bodyspace

myapp/routes/index.js

The file index.js contains logic for the route /.

var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
  res.render('index', { pagetitle: 'Our Farm Stand' });
});
module.exports = router;

The file menu.js contains the JavaScript from the Grid and Flexbox example. It implements the menu toggle function.

function menuToggle(state) {
  var ele = document.getElementById('menu');
  switch(state) {
    case 'show':
      ele.style.opacity=1;
      ele.style.color='rgb(96, 96, 96)';
      ele.style.visibility='visible';
      ele.style.transition='visibility 0s, opacity 0.3s';
      break;
    case 'hide':
      ele.style.opacity=0;
      ele.style.color='black';
      ele.style.visibility='hidden';
      ele.style.transition='visibility 0.3s, opacity 0.3s'; 
      break;
  }
}

myapp/public/stylesheets/style.css

The file style.css contains the CSS from the Grid and Flexbox example.

/* element styles */
* {
  margin: 0;     /* by default, all elements (selector *) have no margin */
}
html {
  width: 100%;                    /* 100% width of parent (root) element */
  height: 100vh;                              /* 100% height of viewport */
  background: rgb(0, 0, 0, 0.1);                            /* 10% black */
  font-size: 1.0em;                                /* our root font size */
  font-family: Arial, Helvetica, sans-serif;             /* default font */
}
body {
  min-height: 100%;
}
section {
  padding: 0.5rem;
  flex-grow: 1;         /* in a flexbox, sections expand along flex axis */
}
h1 {                                           /* Website name in header */
  font-size: 2.0rem;
  font-weight: normal;
}
h2 {                                                   /* About, Contact */
  font-size: 1.25rem;
}
h3 {                                                 /* Section headings */
  font-size: 1.2rem;
  padding: 0.5rem;
}
h4 {                                               /* Section item title */
  font-weight: normal;
  padding: 0.5rem;
}
p {                                                 /* Section item body */
  padding: 0.5rem;
}
a:link, a:visited {            /* anchor links, and visited anchor links */
  color: black;
  text-decoration: none;                            /* disable underline */
}
a:hover {                                 /* when anchor link is hovered */
  color: rgb(25, 25, 25);
}
a:active {                                /* when anchor link is clicked */
  color: rgb(96, 96, 96);
}
/* component styles */
#container {
  display: grid;
  height: 100vh;
  grid-template-columns:
    [left] 10rem auto 10rem [right];
  grid-template-rows:
    [top] 5rem auto 5rem [bottom];     /* header height fits its content */
  grid-template-areas:
    "head head head"
    "panleft mainbody panright"
    "foot foot foot";
}
#header {
  grid-area: head;                    /* corresponds to name in template */
  background: rgb(0, 0, 0, 0.2);                            /* 20% black */
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: baseline;  /* site name and nav item text aligns baseline */
  padding: 1.0rem;
}
#panel {                                       /* for element id="panel" */
  display: flex;                     /* this element is a flexbox parent */
  flex-direction: column;          /* its child elements flex vertically */
  padding: 0.5rem;
  background: rgb(0, 0, 0, 0.1);                            /* 10% black */
}
#panel.left {                 /* for element id="panel" and class="left" */
  grid-area: panleft;                  /* this element fills a grid area */
}
#panel.right {
  grid-area: panright;
}
#footer {
  grid-area: foot;
  display: flex;                     /* this element is a flexbox parent */
  flex-direction: column;          /* its child elements flex vertically */
  justify-content: center;           /* horizontal center footer content */
  align-items: center;                 /* vertical center footer content */
  padding: 0.5rem;
  background: rgb(0, 0, 0, 0.2);
}
#mainbody {                                 /* for element id="mainbody" */
  display: flex;                     /* this element is a flexbox parent */
  flex-direction: column;          /* its child elements flex vertically */
  grid-area: mainbody;
  justify-self: center;          /* fixed-width mainbody always centered */
  width: 100%;
  min-width: 22.5rem;            /* mainbody width can't go < 22.5rem */
}
div#panel,
div#mainbody {                               /* extra space under header */
  padding-top: 0.5rem;
}
#partners, #sections {     /* for element id="partners" or id="sections" */
  display: flex;                     /* this element is a flexbox parent */
  flex-direction: row;           /* its child elements flex horizontally */
  flex-wrap: wrap;           /* its child elements can wrap to next line */
  align-content: flex-start;       /* child elements start in upper left */
}
#partners.wide {           /* for element id="partners" and class="wide" */
  display: none;              /* by default, do not display this element */
}
#menu {
  position: absolute;      /* menu position unaffected by other elements */
  right: 0;                       /* zero pixels from the right boundary */
  background: rgb(239, 239, 239);
  border: 0.15rem solid rgb(0, 0, 0, 0.4);
  visibility: hidden;        /* visibility property supports transitions */
  opacity: 0;      /* opacity + visibility transition = menu fade effect */
  z-index: 1;              /* ensure menu appears over all other content */
}
#menuitems {               /* menu is implemented as a flexbox container */
  display: flex;
  flex-direction: column;
  padding: 1rem;
}
#menuitems h3 {
  border-top: 0.15rem solid rgb(0, 0, 0, 0.1);  /* light horizontal rule */
}
#menuitems .sectrule {
  border-color: rgb(0, 0, 0, 0.25);            /* darker horizontal rule */
}
#menuitems .menuhead {
  border-top: none;
}
#menuitems h3:hover {
  background-color: rgb(0, 0, 0, 0.1);     /* gray of rollover menuitems */
}
.menubutton {
  text-align: right;
  cursor: pointer;            /* indicates it can be clicked like a link */
  user-select: none;            /* user cannot select the button as text */
}
#menuitems .alignright {
  text-align: right;            /* right-aligned menu item text (unused) */
}
#header h1.menubutton {
  display: none;        /* in default view (landscape), hide menu button */
  border: 0.15rem solid rgb(0, 0, 0, 0);   /* (invisible) alignment shim */
}
#header .placeholder {    /* this invisible button is rendered when menu */
  color: rgb(0, 0, 0, 0); /* button is hidden, so header height matches. */
  user-select: none;       /* user can't select text of invisible button */
}
.sectionlink, .partnerlink {
  border-radius: 0.25rem;     /* give this element a slight rounded edge */
  font-weight: normal;
  font-size: 1.1rem;
  padding: 0.5rem;
  width: 7rem;                            /* fixed width for these items */
  margin-bottom: 1rem;                  /* slight margin for readability */
  background: rgb(0, 0, 0, 0.1);
}
.sectionlink:hover, .partnerlink:hover {
  background-color: rgb(0, 0, 0, 0.065);   /* brighten bg on mouse hover */
}
.partnerlink {
  height: 7rem;        /* partner elements are additionally fixed height */
}
.partnerlink.wide {
  margin: 0.5rem 1rem 0.5rem 0;      /* margins for spacing if they wrap */
}
.clickable-area {      /* use whenever a clickable area excludes margins */
  height: 100%;                 /* clickable area spans height of parent */
}
.eventitem, .announceitem, .motditem {
  margin-bottom: 0.5rem;                /* slight margin for readability */
}
.title {                                    /* e.g., "Open for business" */
  font-style: italic;
  font-weight: normal;
  font-size: 1.1rem;
}
.date, .ingredient {                                         /* e.g., January 1, 2021 */
  font-style: italic;
  font-size: 0.9rem;
  padding: 0 0 0.01rem 0.5rem;
  color: rgb(0, 0, 0, 0.5);
}
.navitem {                                             /* About, Contact */
  font-weight: normal;
  padding: 0 0.5rem 0 1rem;
}
.headspace, .panspace, .footspace, .bodyspace {
  flex-grow: 1;      /* these elements expand on flex axis to fill space */
}
/* table styles ("items for sale") */
table {
  border-collapse: collapse;               /* pixel-adjacent table cells */
  width: 100%;
  margin-bottom: 1rem;
}
th {
  text-align: left;
}
tr {
  margin: 4rem 0 0 0;
  border-bottom: 0.15rem solid rgb(0, 0, 0, 0.2);     /* horizontal rule */
}
td, th {
  padding: 0.5rem;
  vertical-align: top;
}
td.price {
  white-space: nowrap;        /* white space in price does not wrap line */
}
td.qty, th.qty {
  text-align: center;
}
span.perunit {
  opacity: 0.5;
}
/* responsive styles applied in portrait mode */
@media screen and (max-width: 45rem) {      /* if viewport width < 45rem */
  #panel.left {
    grid-column-end: left;         /* panel grid area shrinks to nothing */
  }
  #panel.right {
    grid-column-start: right;      /* panel grid area shrinks to nothing */
  }
  #partners.tall {
    display: none;  /* hide partners in panel (overwrites display: flex) */
  }
  #partners.wide {
    display: flex;   /* show partners in body (overwrites display: none) */
  }
  #panel,                                 /* these disappear from layout */
  #header .placeholder,
  .navitem {
    display: none;
  }
  #mainbody {
    grid-column-start: left;         /* mainbody now starts at left edge */
    grid-column-end: right;           /* mainbody now ends at right edge */
  }
  #header h1.menubutton {              /* display the header menu button */
    display: inline;                         /* overwrites display: none */
  }
}

Secondary routes

The following files contain the logic for secondary routes — About, Advice, Contact, etc.

myapp/routes/about.js

var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
  res.render('about', { pagetitle: 'About Us' });
});
module.exports = router;

myapp/routes/advice.js

var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
  res.render('advice', { pagetitle: 'Homesteading Advice' });
});
module.exports = router;

myapp/routes/contact.js

var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
  res.render('contact', { pagetitle: 'Contact Us' });
});
module.exports = router;

myapp/routes/recipes.js

var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
  res.render('recipes', { pagetitle: 'Recipes' });
});
module.exports = router;

myapp/routes/tips.js

var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
  res.render('tips', { pagetitle: 'Tips For Living Well' });
});
module.exports = router;

Secondary views

The following views inherit layout.pug.

myapp/views/about.pug

extends layout
block mainbody
  #mainbody
    section#mainbodyitems
      p Alice &amp; Bob have been operating their farm stand since 1992.

myapp/views/advice.pug

extends layout
block mainbody
  #mainbody
    section#mainbodyitems
      h3 Homesteading Advice
      p Never, ever stand behind a heifer.

myapp/views/contact.pug

extends layout
block mainbody
  #mainbody
    section#mainbodyitems
      h3 Alice &amp; Bob
      p 1344 Chattanooga Way
      p Homestead, VT 05401
      p (802) 555-5555

myapp/views/recipes.pug

extends layout
block mainbody
  #mainbody
    section#mainbodyitems
      h3 Alice's Recipes
      p
        b No-knead next-day dutch oven bread
      p.ingredient 1/4 tsp active dry yeast
      p.ingredient 3 cups all-purpose flour
      p.ingredient 1 1/2 tsp salt
      p.ingredient Cornmeal or wheat bran for dusting
      p In a large bowl, dissolve yeast in water.
      p Add the flour and salt, stirring until blended.
      p Cover bowl. Let rest at least 8 hours, preferably 12 to 18, at warm room temperature, about 70 degrees.
      p When the surface of the dough is dotted with bubbles, it's ready to be folded. Lightly flour a work surface. Sprinkle flour on the dough and fold it over on itself once or twice. Cover loosely and let it rest about 15 minutes.
      p Using just enough flour to keep the dough from sticking, gently shape it into a ball. Generously coat a clean dish towel with flour, wheat bran, or cornmeal. Put the seam side of the dough on the towel. Cover with another towel and let rise for 1 to 2 hours.
      p Heat oven to 475&deg;. Cover and bake for 30 minutes.

myapp/views/tips.pug

extends layout
block mainbody
  #mainbody
    section#mainbodyitems
      h3 Alice's Tips
      p Always rise before the sun.
      p Never use fake maple syrup.
      p If the bear is black, be loud, attack.
      p If the bear is brown, play dead, lie down.

Appearance

In portrait mode, secondary routes are accessed in the menu.

Portrait view

In landscape mode, they're accessible from the header and left panel.

Landscape view