Introduction to Node.js and Express

If you'd like to save on typing, you can download the example code for this class here.

If you followed our web-101 course, then you'll remember using Node.js for our initial learning of Javascript. In that course we didn't go deeply into Node, because we were just using it as a simple way to run Javascript on our computer so we didn't have to worry about the DOM.

In this course we will go a lot more deeply into Node.js and its uses. If you're already familiar with frontend Javascript then Node will be fairly simple to pick up, which is the reason why many companies use it.

But first, let's look at the basics of what Node is and how to use it.

What is Node.js?

Node.js is the most popular Javascript runtime. This means that it provides a way to run Javascript outside of the browser, which allows us to use Javascript to write web servers and other applications which we don't always want to use a browser to run.

Node.js was developed by the Google team and uses the same Javascript engine (v8) as the Chrome web browser, which is developed by some of the world's strongest compiler engineers. This means that it is extremely performant relative to older runtimes such as Microsoft's Chakra (used in old versions of Internet Explorer).

There are other Javascript runtimes such as Bun and Deno, which you might want to explore if you're interested in them. They aim to address some of the shortcomings that developers have found with Node.js.

Your First Backend Project

We're going to set up our first backend project with Node.js! This will be a simple web server with a built-in API, which was the most common way to create web servers before 2010. For learning purposes, this approach is good enough.

First of all, make sure you've downloaded and installed Node.js. If you're on a UNIX system (MacOS or Linux) then the best way to do this is via your package manager. On Windows, it may be more familiar to use the pre-built installer.

First, make a folder for your project. Now use npm init in your project's folder. This will set up a basic package.json for you.

Then run npm install express. This will install the express.js framework, which makes writing a web backend in Node.js substantially easier.

Finally, create a new file called index.js in the same folder.

After you've done this your project folder should look like this:

- index.js
- package.json
- package-lock.json
- node_modules
  - ...lots of folders

The Node.js Project Structure

There are several key components in every Node.js project, and we can see them in our project folder now.

package.json tells npm about your project, its dependencies, and how to run it. For example, if you run npm run test you'll notice that it gives you an error. This is because the default action of the script called test is to print an error and exit. Notice that the section dependencies includes express, which we installed.

package-lock.json gives more detailed information about the dependencies and the specific versions used. It's usually recommended to include this when you upload to a repository like github. This is so that you can guarantee other developers get the same versions of dependencies when they install your project (with npm install), which can help improve stability and security.

node_modules contains the actual code of our dependencies. In this case it's express.js and the dependencies of express. We usually do not upload node_modules to services like github, because it typically contains many files, and it can easily be reconstructed from package.json and package-lock.json, which are far smaller files.

At the moment we have no way to run our project! Let's add one. Change the scripts section of package.json so that it reads:

"scripts": {
  "start": "node index.js",
  "test": "echo \"Error: no test specified\" && exit 1"
}

Now try npm start and you should get an empty output, instead of an error. Note that npm start is a shortcut for npm run start and they'll do exactly the same thing.

Setting Up Our Server

With our project set up, we now need to make it actually do something. First, let's make it give us some output, any output, when we connect to it on the browser.

Put the following in index.js:

const express = require('express')
const app = express()

app.get('/', function (req, res) {
  res.send('Hello World!')
})

app.listen(3000)

Now run npm start, and open localhost:3000 in your web browser. You should see "Hello world!" on a blank webpage.

It's that easy!

Let's break it down line-by-line. First, we import express.js and create a new express app object:

const express = require('express')
const app = express()

Next, we set up our app to listen for an HTTP GET request (more on this next time) on the route /. / is usually the homepage of the website, which in this case is your computer, localhost.

When the server (app) receives a GET request from the browser, it sends a response with the string "Hello world!" and tells the browser its request was successful.

app.get('/', function (req, res) {
  res.send('Hello World!')
})

Finally, we tell Node to listen for requests on port 3000 of our computer (again, we'll go more into ports next time).

app.listen(3000)

Rendering HTML Pages

Being able to output strings of text is great and all, but generally when we're building a web server we want to be able to return webpages. Luckily, webpages are just strings of HTML and Javascript.

So let's create a webpage. Make a directory called web. Inside it, make a file called index.html. Inside the file put the following:

<!DOCTYPE html>

<html>

<head>
  <link rel="stylesheet" href="https://unpkg.com/mvp.css">
</head>

<body>
  <main>
    <h1>
      Hello world!
    </h1>

    <p>
      This is a clean example webpage built with <a href="https://andybrewer.github.io/mvp/">MVP.css</a>.
    </p>
  </main>
  <body>

</html>

If we were using Node.js directly we would need to load our html file and then return it as a response manually. Luckily, Express has a convenient function for this. Change your index.js to read the following way:

const express = require('express')
const app = express()
// this sets the folder we're serving content from
app.use(express.static('web'))

app.get('/', function (req, res) {
  res.sendFile('index.html')
})

app.listen(3000)

Now run the app with npm start and visit the webpage at localhost:3000.

Huzzah! Through the power of express and MVP.css, we've managed to turn our basic server response into a real webpage. We've made a basic web server.

Returning JSON

Normally we don't want to build our own web server just to serve static content like the HTML document we just returned. We want some sort of dynamic behaviour, like loading from a database.

If you've followed most frontend tutorials, they'll teach you how to request data from a server. But what's actually happening on the server can be complex and hard to visualise.

Luckily, most simple web apps will follow the same approach: requesting and returning data in JSON format. The specifics of how that data is rendered vary between frameworks, but most use some form of JSON-based query.

Let's make our frontend request some data from the backend and render it. Change your web/index.html file so it reads the following. The basic ideas being used here should look familiar from previous classes involving querying APIs.

<!DOCTYPE html>

<html>

<head>
  <link rel="stylesheet" href="https://unpkg.com/mvp.css">
  <script>
    async function getContent(route) {
      const $content = document.querySelector("#content")
      const res = await fetch(route)
      const json = await res.json()
      $content.innerHTML = `
      <p><strong>Title:</strong> ${json["title"]}</p>
      <p><strong>Year:</strong> ${json["year"]}</p>
      <p><strong>Description:</strong> ${json["description"]}</p>
      `
    }

    async function requestBook(ev) {
      await getContent("/book")
    }
    async function requestMovie(ev) {
      await getContent("/movie")
    }
  </script>
</head>

<body>
  <main>
    <h1>
      Books and Movies
    </h1>

    <div id="content">
    </div>
    <div>
      <button onclick="requestBook()">Book</button>
      <button onclick="requestMovie()">Movie</button>
    </div>
  </main>

</body>

</html>

On the server side, we need to be able to service our fetch requests, so let's modify index.js so that it returns meaningful JSON. As you may have noticed, the only real change here is calling res.json rather than directly returning the string.

const express = require('express')
const app = express()
app.use(express.static('web'))

app.get('/', function (req, res) {
  res.sendFile('index.html')
})

app.get('/book', function (req, res) {  
  res.json({
    "title": "Love in the Time of Cholera",
    "year": "1985",
    "description": "In their youth, Florentino Ariza and Fermina Daza fall passionately in love. When Fermina eventually chooses to marry a wealthy, well-born doctor, Florentino is heartbroken, but he is a romantic. As he rises in his business career he whiles away the years in 622 affairs—yet he reserves his heart for Fermina. Her husband dies at last, and Florentino purposefully attends the funeral. Fifty years, nine months, and four days after he first declared his love for Fermina, he will do so again."
  })
})

app.get('/movie', function (req, res) {
  res.json({
    "title": "Roman Holiday",
    "year": "1953",
    "description": "A bored and sheltered princess escapes her guardians and falls in love with an American newsman in Rome."
  })
})

app.listen(3000)

Assignment

  1. Add some new books and movies to the web app by adding some new GET routes in index.js and new buttons in index.html.
  2. Update the frontend and backend of the web app to attach images to descriptions of the movies and items. Hint: You can use the link to the image in the same way as you could if was a local file, as express.static has handled this routing for us.