Web Frameworks and Express

Dr. Greg Bernstein

Updated: March 8th, 2020

Web Server Frameworks

Web Server Minimal Features

Basic HTTP and URL Processing

  • Map URL and HTTP Method to some type of action, typically a function call. This process is sometimes called routing.

  • Process HTTP request messages prior to delivering to the action mentioned above. This could be as simple as separating the path portion of a URL or more complicated body processing.

  • Create and augment HTTP response message under the control of the action above to provide a response to the client.

Web Server Advanced/Optional Features

  • Database connectivity, also known as Object Relational Mapping (ORM) for connecting to databases of various types.

  • Content Administration, administrative UI, deployment, etc…

  • Site (outline) generators, RSS Feeds, REST API generators, etc…

“Micro” Frameworks

  • Basic processing only in the box
  • Extended with plugins or middleware
  • Much smaller learning curve if you understand basics of HTTP and URLs
  • Examples: Python’s Flask, JavaScript’s Express.js

Express for Class

We will use the Express.js in this class

  • Smaller learning curve that reinforces what we are learning about HTTP, URLs, etc…

  • Express.js is extremely popular (10 Million downloads a week on NPM) and well tested from a bug and security point of view.

  • Easy to start with, scales to large sites, Node.js deployments supported by many types of hosting services: Heroku, Google App Engine, AWS, Microsoft Azure, etc…

Express.js

References

What is it?

Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.

Installation

npm install express --save

Simplest Example Possible

Put this in a file called hello.js

var express = require('express');
var app = express();

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

app.listen(3000, function () {
    console.log('Example app listening on port 3000!');
});

Run and Visit

  • Runing the server: node hello.js
  • Visiting the site
    • In your browser type the URL: http://localhost:3000/
  • Hopefully you got the text Hello Website Development! in your browser window

localhost?

  • localhost is a defacto standard name used to refer to one of your computers local loopback addresses. It is usually set to 127.0.0.1 in a hosts file buried somewhere on your operating system.
  • You actually have a whole range of loopback addresses you can use: 127.0.0.1 to 127.255.255.254. Loopback addresses are local to your machine!

  • See localhost and loopback addresses, but do not confused loopback with private IP addresses.

Listening on other ports and addresses

// All sorts of express related code
port = 5555; // Or anything you'd like
host = '127.0.0.2'; // Any loopback address
app.listen(port, host, function () {
  console.log(`Example app listening on IPv4: ${host}:${port}`);
});

Hands-on Multiple Servers

  • Make copies of the hello.js file, i.e., hello2.js, hello3.js
  • Change the messages they send a bit
  • Have them listen on different (port, host) combinations
  • Start each in its own command window

Line by Line

  • var express = require('express');:
    • Imports Express
  • var app = express();:
    • Creates an express app

Line by Line

Registers a callback function on the / path to respond to HTTP GET requests:

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

Line by Line

Starts the server listening on the given port and host/IP address:

port = 5555; // Or anything you'd like
host = '127.0.0.2'; // Any loopback address
app.listen(port, host, function () {
  console.log(`Example app listening on IPv4: ${host}:${port}`);
});

A Slightly More Complicated Example

HelloCount.js

const express = require('express');
const app = express();
let count = 0;

app.get('/', function (req, res) {
    count++;
    res.send(`<body><p>Hello CS351!</p>
         <p>This is from the helloCount.js Application.</p>
       <p>This page has been visited <strong>${count} times.</strong></p></body>`);
});

host = '127.0.0.2';
port = '5555';
app.listen(port, host, function () {
console.log(`Example app listening on IPv4: ${host}:${port}`);
});

Key Express Concepts

  • Routes: maps an HTTP method and request URL path to a function to handle the client request

  • Request and Response Objects: We do any work we need with HTTP request and response messages via the “req”, and “res” objects passed to our route handler functions or middleware.

  • Middleware: 3rd party or self written to provide additional capabilities to express by modifying Request and Response objects.

Express Routing

References

Routing?

Application layer routing at the server, not IP routing.

Routing refers to determining how an application responds to a client request to a particular endpoint, which is a URI (or path) and a specific HTTP request method (GET, POST, and so on).

Maps HTTP method, and URL path to a function call

General Form

See app.METHOD

app.METHOD(path, callback [, callback ...])

  • app is an instance of express.
  • METHOD is an HTTP request method, in lowercase, e.g., (get, put,…)
  • path is the path part of the HTTP request URL.
  • callback is the function executed when the route is matched. We can have more than one (see middleware slides.)

Simple Example

From simpleRoute.js

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

app.get('/cs351', function (req, res) {
    res.send('Web Development!');
});

app.get('/wind', function (req, res) {
    res.send('Sorry Dr. B, no wind today');
});

Route Paths

Route paths can be strings, string patterns, or regular expressions.

  • Different server frameworks vary in the types of paths they support.

Simple Match Paths

From file matchRoute.js (try it!):

app.get('/cs351/*', function (req, res) {
    let path = req.path;
    res.send(`Web Development!
        Path = ${path}
    `);
});

app.get('/wind/*', function (req, res) {
    let path = req.path;
    res.send(`Sorry Dr. B, no wind today
    on path = ${path}`);
});

Route Parameters

  • Are named URL segments that are used to capture the values specified at their position in the URL.

  • The captured values are populated in the req.params object, with the name of the route parameter specified in the path as their respective keys.

Route Parameter Example 1

From file parameterRoute.js

// In this route "site" is a parameter
app.get('/tide/:site', function (req, res) {
    let site = req.params.site;
    let tide = Math.random()*5;
    res.send(`<h3>Bad Tide Forecast</h3>
        <p>The tide at <em>${site}</em> will be: </p>
        <p>${tide.toFixed(1)} feet</p>`);
});

Route Parameter Example 1

Running it

Multiple Parameters Example

From file parameterRoute.js

// Two parameters in this path -- "site" and "datetime"
app.get('/wind/:site/datetime/:datetime', function (req, res) {
    let site = req.params.site;
    let datetime = req.params.datetime;
    let wind = Math.random()*20;
    res.send(`<h3>Bad Wind Forecast</h3>
        <p>The wind at <em>${site}</em> at/on
        ${datetime} will be: </p>
        <p>${wind.toFixed(1)} miles per hour</p>`);
});

Multiple Parameters Example

Running it…

Request Information

References

Example Request In Depth

From file requestInfo.js:

app.get('/wind', function(req, res) {
    res.send(info2Html(req));
});

function info2Html(req) {
    let beginning =
`<!DOCTYPE html>
<html lang="en">
    <head><meta charset="utf-8">
        <title>Info on Your Request</title>
    </head><body>`,
        end = `</body></html>`;

    let content = `<p>Method: ${req.method}, HTTP version: ${req.httpVersion}`;
    content += `<p>Client IP: ${req.ip}</p>`;
    content += `<p>Original URL: ${req.originalUrl}</p>`;
      content += `<p>Path: ${req.path}</p>`;
    content += `<p>Query: ${JSON.stringify(req.query)}</p>`;
    content += `<h3>Request Headers:</h3>`;
    for (let h in req.headers){
        content += `<p>${h}:  ${req.headers[h]}</p>`
    }
    return beginning + content + end;
}

Example Run

Responses

References

Response Objects

  • Express creates a Response object (res) for us, that we then can manipulate as needed using their Response API

  • Response objects are manipulated in middleware or your route handler function. You must to send a response of some type from your route handler.

  • Sending a response does not force a return from your route handler, use a return statement for that. Symptom: server error about trying to send a response twice.

Sending a Response 1

See Express Response

  • res.send(body):Sends the HTTP response. The body parameter can be a Buffer object, a String, an object, or an Array.

  • res.json(myJSON): Sends a JSON response. This method sends a response (with the correct content-type) that is the parameter converted to a JSON string using JSON.stringify().

  • res.render(): Renders a view (template) and sends the rendered HTML string to the client.

Sending a Response 2

See Express Response

  • res.redirect(): Redirects to the URL derived from the specified path, with specified status. See Mozilla HTTP redirects for appropriate use.

  • res.sendFile(path [, options] [, fn]): Transfers the file at the given path. Sets the Content-Type response HTTP header field based on the filename’s extension.

Middleware with Express

References

Middleware?

  • Express is a minimalistic but extensible web framework
  • Like applications or other software systems that support “plugins”, Express is extended via middleware.
  • Express middleware are just functions that receive and operated on the HTTP request and response objects provided by Express
  • You can write your own and/or use many external packages.

Example Middleware Functionality

Static Content Middleware

Use to deliver static: HTML, CSS, JS, Images, JS Apps, etc…

To serve static files such as images, CSS files, and JavaScript files, use the express.static built-in middleware function in Express.

Static Middleware Configuration and Behavior

app.use(express.static('dir_name'));
  • Puts static middleware into service
  • With each client request this static middleware will check the URL’s path against the contents of the directory dir_name, if there is a match then that content is returned as the response.
  • Do not include dir_name in paths to static resources in your HTML files or templates.

Example Static Middleware Usage

From file staticAndRoutes.js allows access to assets in the “public” directory. Try it!

var express = require('express');
var app = express();
app.use(express.static('public')); // For static assets

app.get('/', function (req, res) {
    let myObj = {sender: "Dr. B", to: "CS651",
    message: "Hello, code the Web!"};
    res.json(myObj);
});

app.get('/wind', function(req, res) {
    res.send("I'm sorry Dr. B there is no wind right now");
});

Homemade Middleware

Can create your own middleware that does whatever you like and applies to:

  • All requests, good for logging, debugging, sessions,…

  • All requests that correspond to a specific path

  • Just a specific method and path

Homemade Middleware Examples

General and Path Specific Middleware from middlewareEx.js

/*
    Code to illustrate how Express.js middleware works, both general via the
    app.use(fn), and app.use(path, fn) approach.

    Watch the console where you run this file for results.
 */

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

// This middleware will be applied to all requests
const logMiddleware = function(req, res, next) {
    console.log(`log middleware called for original URL: ${req.originalUrl}, path: ${req.path}, IP: ${req.ip}`);
    next();
};

app.use(logMiddleware); // general middleware

const windMiddleware = function(req, res, next) {
    console.log(`wind middleware called, path: ${req.path}`);
    next();
}

app.use('/wind', windMiddleware); // path specific middleware

app.get('/', function(req, res) {
    let message = "You are at the root path, the other paths are: \n";
    message += "/wind, /water";
    res.send(message);
})

app.get('/wind', function(req, res) {
    res.send("You are on the wind path");
});

const whateverMiddle = function(req, res, next) {
    // Add some useless information to the request object
    req.temperature = 50 + 30*Math.random();
    next();
}

// use of method and path specific middleware
app.get('/water', whateverMiddle, function(req, res) {
    res.send(`You are on the water path, the temperature is ${req.temperature}`);
});


const host = '127.0.0.1';
const port = '2222';
app.listen(port, host, function () {
    console.log("middleWareTest.js app listening on IPv4: " + host +
    ":" + port);
});

JSON Based Servers

Why JSON Servers

  • Many web apps just need to send/receive data from a server rather than HTML, CSS, etc.

  • JSON is the most widely used format for non-binary data exchange.

  • Sending and Receiving JSON with Express is easy!

Sending JSON to Clients

See file basicJSONServer.js

var express = require('express');
var app = express();
// Just some stuff to send to a client
let windthings = [{name: "10m Kite", age: 4}, {name: "7.7 Sail", age: 3}];

app.get('/', function (req, res) {
    res.json(windthings); // Turns JS arrays and objects into JSON
                          // Don't have to set headers or JSON.stringify
});

Receiving JSON from Clients

We need some middleware to process the JSON body

// built in middleware is called first then our function
app.post('/addThing', express.json(), function(req, res) {
    // Debugging code to see what the server got
    console.log(`path /addThing received: ${JSON.stringify(req.body)}`);
    windthings.push(req.body); // Adding to our server side array
    res.json(windthings); // returning our server side array via JSON
});

Testing JSON Servers

  • For GET paths we can point our browsers at the appropriate URL (IP address, port, path) or we can write client code.

  • For POST or other requests where we are sending JSON from the server we’ll use a library like request

Send JSON to Server

To test the previous server, file: postTestJSON.js

const postInfo = {url: 'http://127.0.0.1:5555/addThing',
                    method: "POST",
                    json: true,
                    body: {name: "13m kite", age: 2}
};

console.log("POST JSON test");
request(postInfo, function(error, res, body) {
    console.log(error);
    console.log(body);
});