developers

Build a Website with Node.js and Express

Learn how to use Express and Pug to build a website in Node.js

Jun 18, 201918 min read

In this tutorial, you'll learn how to build a simple and clean Node.js server-side rendered application using the Express framework and Pug templates styled with CSS.

Pug knew about Node and Express before it was cool

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

We tested this tutorial using Node.js

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

Bootstrap a Node.js Project

🛠️ Create a project directory named

whatabyte-portal
anywhere in your system and make it your current directory:

mkdir whatabyte-portal
cd whatabyte-portal

🛠️ Execute the following command within the

whatabyte-portal
directory to initialize your Node.js project with default settings:

npm init -y

🛠️ Then, create the entry point of the application, a file named

index.js
:

touch index.js

Create 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

The command above 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": "^2.0.4"
  }
}

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: Prepare 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.

Set Up Express 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 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 you then store 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}`);
});

Run 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.

Use the Pug Template Engine with Express

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, you can't hydrate them with data from the server.

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

🛠️ To use Pug, start by installing the

pug
package using another terminal window:

npm i pug

To optimize page composition through templates, 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.

mkdir views

Under this new directory, create a

layout.pug
file:

touch views/layout.pug

Populate

views/layout.pug
with the following content:

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:

touch views/index.pug

Add the following content to

views/index.pug
:

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 like
layout.pug
, you can define content injection points through a named
block
. Another template, such as
index.pug
, can then inject content into that template by first extending it and then 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. You establish parent-child relationships between elements using indentation.

You can define element attributes, such as

charset
, by placing the attribute key and value next to the element name and enclosing it in parentheses, resembling the structure of a function call:

meta(charset="utf-8")

The content of an element can go next to the element name in the same line or indented below:

h1.Banner WHATABYTE
div.Message
  div.Title
    h3 Making the Best
    h1 Food For Devs

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 tells 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 tells Express what template engine to use, which in this case, is
pug
.

Render 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 representing 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 the controller passes 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; instead, the template it extends,
layout.pug
, uses it.

Refer to Using template engines with Express for more details.

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

When you make any changes in the

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

Add 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

🛠️ Working with Browsersync is easy. Execute the following command to create a configuration file with default options:

browser-sync init

browser-sync init
creates a
bs-config.js
file under your project directory, which contains a large number of default settings. You only need a small subset of those options to use Browsersync to reload your web pages when you change your project's code.

🛠️ Replace the content of

bs-config.js
with the following:

module.exports = {
  proxy: "localhost:8000",
  files: ["**/*.css", "**/*.pug", "**/*.js"],
  ignore: ["node_modules"],
  reloadDelay: 10,
  ui: false,
  notify: false,
  port: 3000,
};
What are these Browsersync configuration options doing?
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.

reloadDelay: 10

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

ui: false

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

notify: false

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

port: 3000

Use a specific port to host the site.

Check the "Browsersync options" document to learn more details about all the configuration options that Browsersync offers you to optimize your development workflow.

🛠️ Next, create an npm script that configures and runs Browsersync to serve your web pages. 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 --config bs-config.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {...},
  "dependencies": {...}
}

🛠️ 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 serving the static site:

[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 how the site served on port
3000
updates itself.

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

Tweet This

Serve Static Assets with Express

To integrate stylesheets and images into the application, you need to configure Express 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:

mkdir public

You'll use

express.static()
, a built-in middleware function that specifies the directory path 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.

Style Express templates using CSS

🛠️ Inside the

public
directory, create a
style.css
file:

touch public/style.css

🛠️ Populate the content of

public/style.css
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 a Pug template in Express using CSS. 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 following to make use of those classes:

Click here to see the content of
style.css
.
@import url("https://fonts.googleapis.com/css?family=Raleway:800|Merriweather+Sans|Share+Tech+Mono");

:root {
  --ui-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.06),
    0 4px 5px 0 rgba(0, 0, 0, 0.06), 0 1px 10px 0 rgba(0, 0, 0, 0.08);
  fill: rgba(0, 0, 0, 0.54);
  --ui-shadow-border: 1px solid rgba(0, 0, 0, 0.14);
}

* {
  box-sizing: border-box;
}

html,
body,
#root {
  height: 100%;
  width: 100%;
}

body {
  margin: 0;
  padding: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
    "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
}

h1,
h2,
h3 {
  font-family: "Raleway", sans-serif;
  text-transform: uppercase;

  padding: 0;
  margin: 0;

  color: #2a3747;
}

h1 {
  font-size: 40px;
}

a {
  color: inherit;
  text-decoration: none;
  cursor: pointer;
  user-select: none;
}

#root {
  display: flex;
  flex-direction: column;
}

.View {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-start;

  height: 100%;
  width: 100%;

  padding: 20px;

  background-size: cover;

  font-family: "Merriweather Sans", sans-serif;
}

.Banner {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;

  width: 100%;

  border-radius: 5px;

  overflow: hidden;
  background: white;
  padding: 15px;

  font-family: "Share Tech Mono", monospace;

  border-bottom: var(--ui-shadow-border);
  box-shadow: var(--ui-shadow);
}

.Message {
  background: white;
  padding: 30px;
  border-bottom: var(--ui-shadow-border);
  box-shadow: var(--ui-shadow);
}

.Message > .Title {
  padding-bottom: 20px;
}

.Message > .Details {
  display: flex;
  flex-direction: column;
  line-height: 1.5em;
}

.NavButtons {
  display: flex;
  width: 100%;
  justify-content: space-around;
  align-items: center;
  padding: 0 20px;
}

.NavButton {
  display: flex;
  justify-content: center;
  align-items: center;

  height: 55px;
  width: 150px;

  background: #fa4141;

  border-radius: 30px;

  font-size: 16px;
  font-weight: bold;
  color: white;

  text-transform: capitalize;

  border-bottom: var(--ui-shadow-border);
  box-shadow: var(--ui-shadow);
}

The new stylesheet uses CSS variables using the

var()
function to keep your code DRY. Internet Explorer doesn't support CSS variables currently, but Edge supports them!

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.

Tweet This

Add 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
    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!

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.

Create More Views with Pug and Express

🛠️ For practice, create a user profile page. Under the

views
directory, create a Pug template file named
user.pug
.

touch views/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-portal/patio.jpeg") 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

Photo by Helena Lopes on Unsplash.

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 Express 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.).

Try out the most powerful authentication platform for free.

Get started →

Sounds exciting? ⚡️

Next Step: I'm ready to secure my Express web appI ran into an issue