Dr. Greg Bernstein
Updated October 18th, 2021
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.
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…
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 is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.
npm install express --save
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!');
});
node hello.mjs
http://localhost:3000/
Hello Website Development!
in your browser windowlocalhost
?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.
// 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}`);
});
hello.mjs
file, i.e., hello2.mjs
, hello3.mjs
import express from 'express';
:
var app = express();
:
Registers a callback function on the /
path to respond to HTTP GET
requests:
app.get('/', function (req, res) {
res.send('Hello Website Development!');
});
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}`);
});
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}`);
});
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.
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
See app.METHOD
app.METHOD(path, callback [, callback ...])
get
, put
,…)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 can be strings, string patterns, or regular expressions.
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}`);
});
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.
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>`);
});
Running it
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}`);
});
Running it…
Express ‘Request’ Object adds information to Node.js’s IncommingMessage
object.
Node.js IncommingMessage object.
req.path
: Contains the path part of the request URL, does not include query string.
req.query
: An object containing a property for each query string parameter in the route. If there is no query string, it is the empty object, {}.
req.body
: Contains key-value pairs of data submitted in the request body. By default, it is undefined, and is populated when you use body-parsing middleware such express.json
or express.urlencoded
.
req.cookies
: When using cookie-parser
middleware, this property is an object that contains cookies sent by the request. If the request contains no cookies, it defaults to {}.
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;
}
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.
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.
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.
res.append()
, res.set()
, res.get()
: Appends (sets, gets) the specified value to the HTTP response header field. If the header is not already set, it creates the header with the specified value. The value parameter can be a string or an array.
res.cookie()
, res.clearCookie()
: Sets (clears) cookie name to value. The value parameter may be a string or object converted to JSON. Note that a wide range of cookie options are supported.
res.type()
: Sets the Content-Type HTTP header to the MIME type…
General website logging: minimal logging example, full featured 3rd party package express-winston
Body processing: JSON extraction express.json (built-in), Post form processing express.urlencoded (built-in), etc…
Header and Security Processing: Cookies - cookie-parser, Sessions - express-session
Serve Static Resources: built-in express.static
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.
app.use(express.static('dir_name'));
dir_name
in paths to static resources in your HTML files or templates.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}`);
});
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
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);
});
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!
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);
});
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}`);
});
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
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));