Web Frameworks and Express

Dr. Greg Bernstein

Updated October 18th, 2021

Web Server Frameworks

Learning Objectives

  • Understand the basic services provided by a “web framework” and create servers with Express.js
  • Understand and use “routing” in the context of Express.js
  • Understand how to obtain HTTP requests, and set HTTP responses
  • Enhance and extend Express.js functionality with middleware

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 and Samples

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

import express from 'express';
const 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

  • Running the server: node hello.mjs
  • 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.mjs file, i.e., hello2.mjs, hello3.mjs
  • 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 I

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

Line by Line II

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 III

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

import express from '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.mjs Application.</p>
       <p>This page has been visited <strong>${count} times.</strong></p></body>`);
});

const host = '127.0.0.2';
const 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

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

import express from 'express';
const app = express();

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

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

app.get('/wind', function(req, res) {
    console.log("Someone tried to get the wind");
    res.send('Sorry Dr. B, no wind today');
});

const host = '127.0.0.1';
const port = '5555';

app.listen(port, host, function() {
    console.log(`simpleRoute.mjs app listening on IPv4: ${host}:${port}`);
});

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.mjs (try it!):

import express from 'express';
const app = express();

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

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}`);
});

const host = '127.0.0.1';
const port = '5555';

app.listen(port, host, function() {
    console.log(`matchRoute.mjs app listening on IPv4: ${host}:${port}`);
});

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

import express from 'express';
const app = express();

app.get('/', function(req, res) {
    res.send(`Hello from Bad Wind/Tide Forecaster!`);
});

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 2

Running it

single parameter

Multiple Parameters Example

From file parameterRoute.mjs

// two parameters here 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>`);
});

const host = '127.0.0.1';
const port = '5555';

app.listen(port, host, function() {
    console.log(`parameterRoute.mjs app listening on IPv4: ${host}:${port}`);
});

Multiple Parameters Example

Running it…

multiple parameters

Request Information

Express Request References

Example Request In Depth

From file requestInfo.mjs:

import express from 'express';
const app = express();

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

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

const host = '127.0.0.1';
const port = '5555';

app.listen(port, host, function() {
    console.log(`requestInfo.mjs app listening on IPv4: ${host}:${port}`);
});

function info2Html(req) { // In general should use templates for this
    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

request info

Responses

Express Response 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(myObj): 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

Middleware 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.mjs allows access to assets in the “public” directory. Try it!

import express from 'express';
const app = express();
app.use(express.static('public')); // For static assets

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

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

const host = '127.0.0.1';
const port = '5556';

app.listen(port, host, function() {
    console.log(`staticAndRoutes.mjs app listening on IPv4: ${host}:${port}`);
});

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

/*
    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.
 */

import express from 'express';
const app = express();

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

app.use(logMiddleware);

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

app.use('/wind', windMiddleware);

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");
});

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

app.get('/water', whateverMiddle, function(req, res) {
    res.send(`You are on the water path, the temperature is ${req.temperature.toFixed(1)}`);
});


const host = '127.0.0.1';
const port = '2222';
app.listen(port, host, function() {
    console.log("middleWareTest.mjs 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.mjs

import express from 'express';
const app = express();
// Just some stuff to send to client instead of a database call
let windthings = [{ name: "10m Kite", age: 4 }, { name: "7.7 Sail", age: 3 }];

app.get('/', function(req, res) {
    res.json(windthings);
});

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) {
    console.log(`path /addThing received: ${JSON.stringify(req.body)}`);
    windthings.push(req.body);
    res.json(windthings);
});

const host = '127.0.0.1';
const port = '5555';

app.listen(port, host, function() {
    console.log(`Basic JSON app listening on IPv4: ${host}:${port}`);
});

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 node-fetch

Send JSON to Server

To test the previous server, file: postTestJSON.mjs

import fetch from 'node-fetch';

let thing = { name: "13m kite", age: 2 };

fetch('http://127.0.0.1:5555/addThing', {
        method: 'post',
        body: JSON.stringify(thing),
        headers: { 'Content-Type': 'application/json' },
    })
    .then(res => res.json())
    .then(json => console.log(json));
// reveal.js plugins