In this tutorial, you'll learn how to build a simple yet stylish Node.js web application using the Express framework. You'll define an API using HTTP utility methods from Express and you'll create a server-side rendered client to consume your API using Pug templates styled with CSS.

As a bonus, you'll learn how to streamline your Node.js development workflow by using nodemon to restart the server and browser-sync to reload the browser whenever relevant source files change.

Look for the 🛠️️ emoji if you'd like to skim through the content while focusing on the build steps.

What You Will Build

You'll build a login portal for a restaurant named WHATABYTE using server-side rendering (SSR):

Styled page created with background image using Pug, Express, and CSS

Prerequisites

  • Basic understanding of Node.js and JavaScript.

  • Node.js v10+ and a Node.js package manager installed locally, such as npm.

This tutorial was tested using Node.js v10.16.3 and npm v6.9.0.

If you need to install Node.js and npm, use any of the official Node.js installers provided for your operating system.

Bootstrapping a Node.js Project

🛠️ Create a project directory named whatabyte-portal anywhere in your system. Using the terminal, make that directory your current directory and execute the following command to initialize your Node.js project with default settings:

npm init -y

🛠️ Then, under the project directory, create the entry point of the application, a file named index.js.

Creating an npm script to run the application

You'll use nodemon to monitor your project source code and automatically restart your Node.js server whenever it changes.

🛠️ As such, install nodemon as a development dependency:

npm i -D nodemon

This is the equivalent of running npm install --save-dev nodemon.

🛠️ Create a dev script command in your package.json file to run nodemon and delete the test script:

{
  "name": "whatabyte-portal",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "nodemon ./index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "nodemon": "^1.19.3"
  }
}

nodemon gets as an argument the entry point of an application and executes it.

Testing Node.js applications is out of the scope of this tutorial. However, it's important to implement testing to create robust, production-ready web apps.

You now have the basic structure of a Node.js application.

Bonus: Preparing for Git Version Control

🛠️ Create a .gitignore file under the project directory to avoid committing unnecessary or sensitive files. Populate that file with the content of this Gist or feel free to use gitignore.io to create your own custom .gitignore file.

Integrating the Express Web Framework with Node.js

🛠️ To use the Express framework in your application, install it as a project dependency:

npm i express

🛠️ Open index.js and populate it with the following template that defines the core structure of an Express application:

// index.js

/**
 * Required External Modules
 */

/**
 * App Variables
 */

/**
 *  App Configuration
 */

/**
 * Routes Definitions
 */

/**
 * Server Activation
 */

🛠️ Now, under the Required External Modules section, import the express and path packages:

// index.js

/**
 * Required External Modules
 */

const express = require("express");
const path = require("path");

The path module provides you with utilities for working with file and directory paths. You'll use it later on to create cross-platform file paths to access view templates and static assets, such as stylesheets and images.

🛠️ Next, under the App Variables section, add the following:

// index.js

/**
 * App Variables
 */

const app = express();
const port = process.env.PORT || "8000";

Here, you execute the default function exported by the express module to create an instance of an Express application, which is then stored in app. You also define the port the server will use to listen for requests: its value is process.env.PORT, if available, or 8000 by default.

🛠️ Under the Routes Definitions section, create a simple route handler for the HTTP GET / request that replies with a string.

// index.js

/**
 * Routes Definitions
 */

app.get("/", (req, res) => {
  res.status(200).send("WHATABYTE: Food For Devs");
});

Refer to Express Basic Routing for more details.

🛠️ Finally, under the Server Activation section, start a server listening for incoming requests on port and to display a message to confirm it's listening:

/**
 * Server Activation
 */

app.listen(port, () => {
  console.log(`Listening to requests on http://localhost:${port}`);
});

Running an Express application

🛠️ Execute dev to test the script and run the app:

npm run dev

🛠️ Visit http://localhost:8000/ to see the app in action. The browser should display the string "WHATABYTE: Food For Devs" on a plain page.

Using the Pug Template Engine to Create Express Views

Modern web apps are data-driven and need views that can display data dynamically. While you can create good-looking views using static HTML templates, they can't be hydrated with data from the server.

Instead, you use template engines like Pug to create dynamic views that can render UI elements conditionally and be hydrated with values from the server. What makes Pug stand out from other offerings is its terse syntax and its support for template inheritance to easily compose pages.

🛠️ To use Pug, start by installing pug using another terminal window:

npm i pug

To optimize template composition, you'll use template inheritance to extend a core layout template that encapsulates the top-level HTML structure of a page.

🛠️ As such, create a directory named views under the project directory to store all of your templates. Under this new directory, create a layout.pug file and add the following content to it:

block variables
doctype html
html
  head
    meta(charset="utf-8")
    link(rel="shortcut icon", href="/favicon.ico")
    meta(name="viewport", content="width=device-width, initial-scale=1, shrink-to-fit=no")
    meta(name="theme-color", content="#000000")
    title #{title} | WHATABYTE
  body
    div#root
      block layout-content

Notice that this template uses the title variable to render the document title of a page. This variable value will be passed from the server to the template by route handlers defined in the API, also known as controllers.

🛠️ Next, create an index.pug file under the same directory and add the following content to it:

extends layout

block layout-content
  div.View
    h1.Banner WHATABYTE
    div.Message
      div.Title
        h3 Making the Best
        h1 Food For Devs
      span.Details Access the WHATABYTE Team Portal
    div.NavButtons
      a(href="/user")
          div.NavButton Just dive in!

With these two templates, you are using the block and extends artifacts to implement template inheritance. Within a template, you can define content injection points through named blocks. A template can then inject content into the template it extends by defining the content of the named block.

Pug templates don't use HTML tags, instead, they simply use the names of HTML elements and whitespace to define their structure. Attributes are placed next to an element name and enclosed in parenthesis, resembling the structure of a function call. The content of an element can go next to the element name in the same line or indented below.

Visit Getting Started for a complete overview of how Pug works.

To connect the templates with the controllers, you need to configure Express to use Pug as the view template engine.

🛠️ Open index.js and add the following under the App Configuration section:

// index.js

/**
 *  App Configuration
 */

app.set("views", path.join(__dirname, "views"));
app.set("view engine", "pug");

Here, you use app.set(name, value) to assign a setting name to a value. Some setting names are reserved by Express to configure the behavior of the app, such as views and views engine.

The views setting is used to tell Express what directory it should use as the source of view template files. In this case, you set the views directory as the source using the path.join() method, which creates a cross-platform file path.

The view engine setting is used to tell Express what template engine to use, which in this case is pug.

Rendering Pug views as client responses

🛠️ Next, you'll refactor the GET / controller to make it render index.pug as the client response. Under the Routes Definitions section, update your route handler as follows:

// index.js

/**
 * Routes Definitions
 */

app.get("/", (req, res) => {
  res.render("index", { title: "Home" });
});

The first argument of res.render(view) is a string that represents the file path of a template, relative to the templates source directory, views. It renders the file and sends the rendered HTML as a string to the client. The extension for the template file defaults to .pug as Pug is the default view engine.

Its second argument is optional and represents an object that is passed from the controller to the template. The properties of this object become local variables in the template. Therefore, { title: "Home" } defines a local title variable in index.pug. However, index.pug doesn't use title locally; this value is used by the template it extends: layout.pug.

Refer to Using template engines with Express for more details.

🛠️ Refresh the browser to see the new page rendered on the screen.

Basic page created using Pug, Express, and Node

Make any changes in the index.pug template and refresh the browser to observe the changes made. However, manually refreshing the browser to see updates slows down your development process. To overcome that, you'll use Browsersync.

Adding Live Reload to Express Using Browsersync

You can emulate the live reload behavior of front-end frameworks such as React and Angular in Express templates using Browsersync: when a CSS rule or the return value of a function is changed, the browser refreshes automatically.

🛠️ Start by installing Browsersync as follows:

npm i -D browser-sync

🛠️ Next, create an npm script that configures and runs Browsersync to serve your web application. Open and update package.json with the following ui npm script:

{
  "name": "whatabyte-portal",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "nodemon ./index.js",
    "ui": "browser-sync start --proxy=localhost:8000 --files='**/*.css, **/*.pug, **/*.js' --ignore=node_modules --reload-delay 10 --no-ui --no-notify"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {...},
  "dependencies": {...}
}

The ui script uses a couple of Browsersync command-line options to configure the behavior of Browsersync:

browser-sync start --proxy=localhost:8000

Proxy the Node/Express app served on localhost:8000 as Browsersync only creates a static server for basic HTML/JS/CSS websites. You still need to run the server with nodemon separately.

--files='**/*.css, **/*.pug, **/*.js'

Using glob patterns, specify the file paths to watch. You'll watch CSS, Pug, and JavaScript files.

--ignore=node_modules

Specify the patterns that file watchers need to ignore. You want to ignore node_modules to let Browsersync run fast.

--reload-delay 10

Time in milliseconds to delay the reload event following file changes to prevent Nodemon and Browsersync from overlapping, which can cause erratic behavior.

--no-ui

Don't start the Browsersync user interface, a page where you can control the behavior of Browsersync.

--no-notify

Disable a distracting message notifying you that Browsersync is connected to the browser.

🛠️ To serve the user interface of your Express app, run the following command in a separate terminal window:

npm run ui

The terminal will output information about the local and external locations where the static site is being served:

[Browsersync] Proxying: http://localhost:8000
[Browsersync] Access URLs:
 -------------------------------
    Local: http://localhost:3000
 External: http://<YOUR IP ADDRESS>:3000
 -------------------------------
[Browsersync] Watching files...

🛠️ Browsersync automatically opens a new window presenting your interface. If it didn't, open http://localhost:3000/. Make any change on the index.pug file, save it, and watch the site served on port 3000 update itself.

"Learn how Browsersync lets you streamline your development workflow when creating server-side rendered applications with Node and Express."

Serving Static Assets with Express

To integrate stylesheets and images into the application, Express needs to be configured to access and serve static files from a project directory — a process similar to configuring the render engine.

🛠️ For your application, create a directory named public under the project directory to serve this purpose.

You'll use express.static(), a built-in middleware function that specifies the path of the directory from which to serve static assets.

🛠️ Head back to index.js and update the App Configuration section as follows:

// index.js

/**
 *  App Configuration
 */

app.set("views", path.join(__dirname, "views"));
app.set("view engine", "pug");
app.use(express.static(path.join(__dirname, "public")));

To mount the express.static() middleware function, you use app.use(). You can now put inside the public directory any CSS or image files that your application needs to use.

Styling Express templates using CSS

🛠️ Inside the public directory, create a style.css file and update its content as follows:

/* public/style.css */

body {
  background: aqua;
}

🛠️ Save your changes. To use this stylesheet, you need to link to it from a template file. Update layout.pug as follows:

block variables
doctype html
html
  head
    meta(charset="utf-8")
    link(rel="shortcut icon", href="/favicon.ico")
    meta(name="viewport", content="width=device-width, initial-scale=1, shrink-to-fit=no")
    meta(name="theme-color", content="#000000")
    title #{title} | WHATABYTE
    link(rel="stylesheet" href="/style.css")
  body
    div#root
      block layout-content

🛠️ Save your changes again. Observe how Browersync reloads the browser and the background color of the home page changes. This is the foundation of styling an Express template. You can add as many other rules and stylesheets as you desire.

🛠️ The Pug templates you created have CSS classes in their markup to define their layout and presentation. Replace the content of style.css with the content presented on this Github gist to put those classes to use.

The new stylesheet uses CSS variables using the var() function to keep your code DRY. CSS variables are currently not supported by Internet Explorer but they are supported by Edge!

With this new stylesheet applied, the index page looks much nicer:

Styled page created using Pug, Express, and Node, and CSS

"Learn how to style Express templates using Pug along with CSS variables"

Adding images to an Express app

You can place image files within the public directory and then reference their relative path from any element where you want to use them. Use an image as the background of the div.View container in index.pug.

🛠️ To start, right-click on the following image and save it as food.jpg inside the public directory.

Cover image presenting food items

Photo by Caesar Aldhela on Unsplash

🛠️ Next, open public/style.css and append to the file a .WelcomeView class as follows:

.WelcomeView {
  background: url("food.jpg") left bottom;
  background-size: cover;
}

🛠️ Then, open views/index.pug and append this new class to the div.View container. It should now look like this:

extends layout

block layout-content
  div.View.WelcomeView
  // rest of the template...

Don't forget that indentation is critical for proper template structure with Pug. Indenting creates a child HTML element.

Based on the configuration of this Express app, the url value of the background property references the image using a file path relative to the public directory. You also specify the image position, left bottom, and its presentation, cover, which prevents stretching.

You should now have this snazzy image as the background of the index page.

Styled page created with background image using Pug, Express, and CSS

🛠️ Next, save this icon file as favicon.ico under the public directory to get a logo in the browser tab. Hard reload your browser to see this particular change.

Creating Additional Views with Express and Pug

🛠️ For practice, create a user profile page. Under, the views directory, create a Pug template file named user.pug. Populate this new template with the following:

extends layout

block layout-content
  div.View.UserView
    h1.Banner Howdy, #{userProfile.nickname}!
    div.Message
      div.Title
        h3 Making Us the Best
        h1 Teammate Profile
      pre.Details=JSON.stringify(userProfile, null, 2)
    div.NavButtons
      a(href="/logout")
        div.NavButton Log out

🛠️ The local userProfile variable will be provided to this view by the controller that renders it. To create that controller, open index.js and update the Routes Definitions section as follows:

// index.js

/**
 * Routes Definitions
 */

app.get("/", (req, res) => { ... });

app.get("/user", (req, res) => {
  res.render("user", { title: "Profile", userProfile: { nickname: "Auth0" } });
});

Once again, you use the second parameter of res.render() to pass data from the controller to the template. In this case, you are passing mock user information to the user profile template.

🛠️ Use the UserView class defined in the user template to style it. Open public/style.css and append the following rule:

.UserView {
  background: url("https://cdn.auth0.com/blog/whatabyte/patio.png") left bottom;
  background-size: cover;
}

🛠️ As an alternative to referencing images within the public directory, you can use the URL of a hosted image. Visit http://localhost:3000/user and see the user view rendered with a background image and filled with mock user data.

Another page created with background image using Pug, Express, and CSS

Recap

You used Node.js, Express, Pug, and CSS to create a web application that renders a stylish user interface with dynamic data by communicating with an API. You are also serving static assets from the server hosting the API.

As a next step, you can learn how to add authentication to this app using Passport.js and Auth0. Securing Node.js applications with Auth0 is easy and brings a lot of great features to the table.

After you sign up for a free Auth0 account, you only need to write a few lines of code to get a solid identity management solution with support for single sign-on, social identity providers (like Facebook, GitHub, Twitter, etc.), and enterprise identity providers (like Active Directory, LDAP, SAML, custom, etc.).

Sounds exciting? Then, head to Create a Simple and Secure Node Express App and get started. ⚡️