CSE 154

Lecture 16: Intro to Node.js

servers

XKCD 869

Agenda

Introduction to Module 4 and Node.js!

Review: Web Services

Web service: software functionality that can be invoked through the internet using common protocols

It's like a remote function(s) you can call. Done by contacting a program on a web server

  • Web services can be written in a variety of languages
  • Many web services accept parameters and produce results
  • Clients contact the server through the browser using XML over HTTP and/or AJAX Fetch code
  • The service's output might be HTML but could be text, XML, JSON, or other content

Some Web Services We've Used as Clients

Merriam-Webster Dictionary API

NASA APOD API

Trivia API

CSE154 web services:

  • pokedex.php and game.php
  • wpl.php
  • mowgliscafe.php

So How Does a Web Service (like wpl.php) Respond to Requests?

full stack analogy

Image source

CSE 154 Modules: Where We're At

  1. Webpage structure and appearance with HTML5 and CSS.
  2. Client-side interactivity with JS DOM and events.
  3. Using web services (API's) as a client with JS.
  4. Writing RESTful web services with Node.js and Express.
  5. Storing and retrieving information in a database with MySQL and server-side programs.

URLs and Web Servers

https://server/path/file

Usually when you type a URL in your browser:

  1. Your computer looks up the server's IP address using DNS
  2. Your browser connects to that IP address and requests the given file
  3. The web server software (e.g. Apache) grabs that file from the server's local file system and then send back its contents to you

Some URLs actually specify programs that the web server should run, and then send their output back to you as the result:

https://courses.cs.washington.edu/courses/cse154/webservices/pokedex/pokedex.php?pokedex=all

The above URL tells the server courses.cs.washington.edu to run the program pokedex.php and send back its output

Why Do We Need a Server to Handle Web Service Requests?

server analogy

Servers are dedicated computers for processing data efficiently and delegating requests sent from many clients (often at once).

These tasks are not possible (or appropriate) in the client's browser.

Languages for Server-Side Programming

server side languages

Server-side programs are written using programming languages/frameworks such as PHP, Java/JSP, Ruby on Rails, ASP.NET, Python, Perl, and JS (Node.js)

Web servers contain software to run those programs and send back their output.

Our (New) Server-Side Language: JS (Node)

NodeJS logo
  • Open-source with an active developer community
  • Flourishing package ecosystem
  • Designed for efficient, asynchronous server-side programming
  • You can explore other server-side languages after this course!

What is Client-Side JS?

So far, we have used JS on the browser (client) to add interactivity to our web pages

"Under the hood", your browser requests the JS (and other files) from a URL resource, loads the text file of the JS, and interprets it realtime in order to define how the web page behaves.

In Chrome, it does this using the V8 JavaScript engine, which is an open-source JS interpreter made by Google. Other browsers have different JS engines (e.g. Firefox uses SpiderMonkey).

Besides the standard JS language features, you also have access to the DOM when running JS on the browser - this includes the window and document

Client vs. Server-side JS

client vs. server-side js

Node.js: Server-side JS

Node.js uses the same open-source V8 JavaScript engine as Chrome

Node.js is a runtime environment for running JS programs using the same core language features, but outside of the browser.

When using Node, you do not have access to the browser objects/functions (e.g. document, window, addEventListener).

Instead, you have access to functionality for managing HTTP requests, file i/o, and database interaction.

This functionality is key to building REST APIs!

Getting started with Node.js

When you have Node installed (last week's section), you can run it immediately in the command line.

  1. Start an interactive REPL with node (no arguments). This REPL is much like the Chrome browser's JS console tab.
  2. Execute a JS program in the current directory with node file.js

Starting a Node.js Project

There are a few steps to starting a Node.js application, but luckily most projects will follow the same structure.

When using Node.js, you will mostly be using the command line (e.g. php-ide-terminal you should have installed in Atom).

  1. Start a new project directory (e.g. node-practice)
  2. Inside the directory, run npm init to initialize a package.json configuration file (you can keep pressing Enter to use defaults)
  3. Install any modules with npm install <package-name>
  4. Write your Node.js file! (e.g. app.js)
  5. Include any front-end files in a public directory within the project.

Along the way, a tool called npm will help install and manage packages that are useful in your Node app.

Starting a Node.js Project

Running npm init to create package.json

Starting an empty project

Starting a Node.js Project

Running npm init to create package.json

Running npm init

Starting app.js

You can write and execute JS using Node in the command line

Running node program on the command line

Node.js Modules

When you run a .js file using Node.js, you have access to default functions in JS (e.g. console.log)

In order to get functionality like file i/o or handling network requests, you need to import that functionality from modules - this is similar to the import keyword you have used in Java or Python.

In Node.js, you do this by using the require() function, passing the string name of the module you want to import.

For example, the built-in module to provide HTTP request/response functionality in Node.js is called http. You can import it like this:

const http = require("http");

JS

Quick Note on const Keyword

Using const to declare a variable inside of JS just means that you can never change what that variable references. We've used this to represent "program constants" indicated by ALL_UPPERCASE naming conventions

For example, the following code would not work:

const specialNumber = 1;
specialNumber = 2; // TypeError: Assignment to constant variable.

JS

When we store modules in Node programs, it is conventional to use const instead of let to avoid accidentally overwriting the module.

Unlike the program constants we define with const (e.g. BASE_URL), we use camelCase naming instead of ALL_CAPS.

Our first Node server

The http core module provides functionality for handling HTTP requests.

To get started with a simple server, we can write the following Node.js program:

"use strict";
const http = require("http");

let server = http.createServer((request, response) => {
  console.log("I got your request!");
  response.write("Hello from a Node Server!");
  response.end();
});

const PORT = process.env.PORT || 8000;
server.listen(PORT);

JS

Try it! Save http-server-example.js and then run node http-server-example.js in the command line when your current

What do you see in the browser? What do you see in the command line console?

APIs in Practice

As we add endpoints and different types of requests to our APIs, the http module can become very complicated. We will instead use the most popular web service module to implement our Node.js APIs, which is built using the same http module but with functionality that is much easier to use.

But unlike http, Express is not a core module - it has been developed by a community of developers consistently updating it.

But how can we use modules like Express in our own code?

npm: Node Package Manager

npm is the Node Package Manager

Node comes with built-in "core modules" you can require in your project without extra installation (http, fs, path, etc.)

But one of the appealing features of Node as a server-side technology is the extensive ecosystem of modules like Express to accomplish different tasks

npm (Node Package Manager), gives us an easy way to download, manage, and update all of these packages.

Installing Packages

Node packages can be installed in two different ways, globally or locally.

Global packages are installed by adding a -g flag in the npm install command and are accessible anywhere on your computer.

npm install -g <package_name>

command line

This is useful if a package contains terminal commands that you want to be used at any time, such as the programs you installed last week (nodemon and ndb).

Local Packages

It's best practice to use local installation for project-specific packages.

For example, we will use the Express module to build RESTful APIs in Node - each time we create a project, we will run the following within the project directory:

npm install express

npm will automatically add a dependency to the project's package.json

Package Management

In addition to installing modules, npm will also automatically manage packages in your Node projects.

Whenever you install a package, npm will add it to package.json and a node_modules folder

These files should not be distributed with the rest of your project code (e.g. front-end files and app.js - node_modules can get very large

Instead of sending this node_modules folder containing modules that already exist online, you send them an information file called package.json that NPM uses to keep track of all of these packages.

When a user downloads your project and its package.json file, they just run npm install within the project directory, and npm will install all of those packages required to run the project.

npm's package.json

The package.json file is created by running npm init which will prompt the user to answer a few questions about the project like the name and version.

Any future npm install <package_name> commands that are run in the same project will be automatically added to this package.json file.

A package-lock.json file will also be created, containing a more detailed version of all of the packages used in a project and their dependencies.

Express.js

Express.js is one of the most popular Node frameworks used to implement APIs

Serves as an easy-to-use wrapper around Node's more complex core networking modules.

Provides simple functionality through "middleware" to listen and respond to HTTP requests from clients at different endpoints.

Middleware

Middleware is a term you'll see when working with Express - it refers to any function that works "in the middle" of communication between the client and Node.js.

Every middleware function has access to the request and response objects, and usually modifies the response to send.

Express as a Routing Mechanism for Node.js

Request flow with express and node.js

Image source

Installing express module locally

To use Express, you must install it in the project directory with npm install express. This will automatically update package.json to include the dependency, and also create a node_modules directory (other modules you might install will also be added to package.json and node_modules).

Running node program on the command line

Starting app.js as an Express REST API

You can write and execute JS using Node in the command line

Running express app with nodemon

app.listen()

To start the localhost server to run your Express app, you need to specify a port to listen to.

The express app object has a function app.listen which takes a port number and optional callback function

At the bottom of your app.js, add the following code - (process.env.PORT is needed to use the default port when hosted on an actual server)

const PORT = process.env.PORT || 8000; // Allows us to change the port easily by setting an environment variable, so your app works with our grading software
app.listen(PORT);

Basic Routing in Express

Routes are used to define endpoints in your web service

Express supports different HTTP requests - we will learn GET and POST

Express will try to match routes in the order they are defined in your code

Adding Routes in Express.js

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

JS

  • app.get allows us to create a GET endpoint. It takes two arguments: The endpoint URL path, and a callback function for modifying/sending the response.
  • req is the request object, and holds items like the request parameters.
  • res is the response object, and has methods to send data to the client.
  • res.set(...) sets header data, like "content-type". Always set either "text/plain" or "application/json" with your response.
  • res.send(response) returns the response as HTML text to the client.
  • res.json(response) Does the same, but with a JSON object.

When adding a route to the path, you will retrieve information from the request, and send back a response using res (e.g. setting the status code, content-type, etc.)

If the visited endpoint has no matching route in your Express app, the response will be a 404 (resource not found)

Useful Request Properties/Methods

Name Description
req.params Endpoint "path" parameters from the request
req.query query parameters from the request

Useful Response Properties/Methods

Name Description
res.write(data) Writes data in the response without ending the communication
res.end() Ends the process
res.send() Sends information back (default text with HTML content type)
res.json() Sends information back as JSON content type
res.set() Sets header information, such as "Content-type"
res.type() A convenience function to set content type (use "text" for "text/plain", use "json" for "application/json")
res.status() Sets the response status code
res.sendStatus() Sets the response status code with the default status text

Setting the Content Type

By default, the content type of a response is HTML - we will only be sending plain text or JSON responses though in our web services

To change the content type, you can use the res.set function, which is used to set response header information (e.g. content type).

You can alternatively uses res.type("text") and res.type("json") which are equivalent to setting text/plain and application/json Content-Type headers, respectively.

app.get('/hello', function (req, res) {
  // res.set("Content-Type", "text/plain");
  res.type("text"); // same as above
  res.send('Hello World!');
});

Setting plain text response

app.get('/hello', function (req, res) {
  // res.set("Content-Type", "application/json");
  rese.type("json");
  res.send({ "msg" : "Hello world!" });
  // can also do res.json({ "msg" : "Hello world!"});
  // which also sets the content type to application/json
});

Setting JSON response content type

Adding our first "root" route

When you add a route in your Express app (here, we use '/' for the most basic "root" route), you can use res.send to send a response to the client (browser) and view using the port (here, 8000 on localhost)

Viewing app output on localhost

Adding another route

You can add more routes in your file, and Express will use the first one matching the request path

Viewing app output on localhost

Request Parameters: Path Parameters

Act as wildcards in routes, letting a user pass in "variables" to an endpoint

Define a route parameter with :param

Route path: /states/:state/cities/:city
Request URL: http://localhost:8000/states/wa/cities/Seattle
req.params: { "state": "wa", "city": "Seattle" }

These are attached to the request object and can be accessed with req.params

app.get("/states/:state/cities/:city", function (req, res) {
  res.type("text");
  res.send("You sent a request for " + req.params.city + ", " + req.params.state);
});

JS

Adding route parameters: Example

Viewing app output on localhost

Request Parameters: Query Parameters

You can also use query parameters in Express using the req.query object, though they are more useful for optional parameters.

Route path: /cityInfo
Request URL: http://localhost:8000/cityInfo?state=wa&city=Seattle
req.query: { "state": "wa", "city": "Seattle" }
app.get("/cityInfo", function (req, res) {
  let state = req.query.state; // wa
  let city = req.query.city;   // Seattle
  // do something with variables in the response
});

JS

Unlike path parameters, these are not included in the path string (which are matched using Express routes) and we can't be certain that the accessed query key exists.

If the route requires the parameter but is missing, you should send an error to the client in the response.

Setting Errors

The Response object has a status function which takes a status code as an argument.

The 400 status code is what we'll use to send back an error indicating to the client that they made an invalid request.

A helpful message should always be sent with the error.

app.get("/cityInfo", function (req, res) {
  let state = req.query.state;
  let city = req.query.city;
  if (!(state && city)) {
    res.status(400).send("Error: Missing required city and state query parameters.");
  } else {
    res.send("You sent a request for " + city + ", " + state);
  }
});

JS

Summary of Building an Express App

  1. Create a file (e.g. app.js)
  2. Add required modules at the top (at minimum, require(“express”))
  3. Create an app instance: const app = express();
  4. At the end of the file, listen to a port (e.g. 8000)
  5. Add routes! Remember that “/” stands for the basic root route, which can be visited in your browser at localhost:8000/ when your app is running.