Web Request Response Cycle

User

Client (Browser)

DNS Server 1

"You requested cityexplorer.com. I don't know where to find that."

"I know that URL!

The IP Address of cityexplorer.com is 255.255.255.255."

"I will now make an
HTTP request to cityexplorer.com/"

City Explorer's Server

DNS Server 2

"I've never heard of cityexplorer.com. Help!"

"Cool, I'm going to save that in my memory for next time"

"I found the path you requested. Here are the files (HTML, CSS, JS, Json and images) you need."

City Explorer's Database

"I will now make an
HTTP request to cityexplorer.com/location?city=Chicago"

"Let me check my database to see if I know Chicago's latitude and longitude"

"Nope"

Location IQ API

Ok, I will ask my friends at locationiq.com

"41.8837736,
-87.622191"

Open Weather API

"Cloudy, with a chance of rain"

[After storing this data] What's the weather forecast at that location?

Here are the HTML, CSS, JS, Json, and Images you need.

"I better take my umbrella"

"What's the weather today?"

[types "Chicago" in the search bar]

Response Codes

Web Server

  • Our web server needs to start with a package.json file to store project configurations. Create one with this command:

    • npm init -y
    • The -y command accepts all of the default settings and sets up a package.json file in our project with reasonable defaults (the -y is short for yes).
    • If you leave out -y you'll be asked to enter a bunch of stuff like the package name, version, description, etc. Press enter to accept the default values. The "main" entry point can be server.js or index.js (Line 13), which you'll need to create.
  • To run our server we need two libraries. One called "dotenv" and another called "express". Install them both with this command:
    • npm install dotenv express
    • Do not use "-g" because we do not want to install these globally
{
  "name": "deployment",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "dependencies": {
    "dotenv": "^8.2.0",
    "express": "^4.17.1"
  },
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Web Server

  • A list of dependencies in package.json, package-lock.json, and node_modules folder will be added automatically

    • ​Dependencies are used to specify any other modules that a given module (represented by the package.json) requires to work.

    • If you clone a project that comes with a server.js file, it will likely have dependencies in it. When you run "npm install", it will install any modules in the package.json dependencies list.

    • A package-lock.json file ensures that future installations of the project use the same versions of the dependencies.

  • Be sure your project contains an .eslintrc.json, .gitignore, and .env file.
    • ​The .gitignore file should prevent the node_modules folder and .env files from getting committed.
    • If you type "git status" those files should not be listed.
    • This is important because we will later be adding sensitive API keys to the .env file and we don’t want to publish this file to GitHub publicly.

server.js

  • Our server requires 5 sections:

    • Application dependencies (Line 3)

      • Import the modules needed for our application to run

    • Application setup (Line 5)

      • Make an express app that is configured to whatever environment we are using

    • Route definitions (Line 7)

      • Define what path(s) will respond to the user upon request

    • Route handlers (Line 9)

      • Callback functions that run after the route is requested

    • App listener (Line 11)

      • Make sure the server is listening for requests

'use strict';

// Application Dependencies

// Application Setup 

// Route Definitions

// Route Handlers

// App listener

server.js

  • First we need to require express and assign the returned function object to a variable (Line 4).

    • This code tells node to go into the node_modules folder and make express available in our project.

  • The next thing we do is make an app from express (Line 7).

  • Next we need a port to listen to network requests (Line 8).

  • Lastly, we need to tell the server to listen on that port and return a message letting us know when the server is on. (Line 15).

    • This line will always be the last line of the server code.

  • ​Run this command to process the start script from package.json

    • npm start 
'use strict';

// Application Dependencies
const express = require('express');

// Application Setup 
const app = express();
const PORT = 3000;

// Route Definitions

// Route Handlers

// App listener
app.listen(PORT,() => console.log(`Listening on port ${PORT}`));

server.js

  • I use the computer’s terminal to start the server and I use VS Code’s terminal to do ACPs. Having multiple terminal instances open is useful.

  • In a web browser navigate to localhost:3000 or 127.0.0.1:3000

  • If you know your computer’s local IP address (something like 192.168.NNN.NNN), you can open another device connected to your network and type 192.168.NNN.NNN:3000 to view the server.

    • On Windows open up the Command Prompt via your Windows Start menu. Type in “ipconfig” and hit Enter. Look for the line that reads “IPv4 Address.” The number across from that text is your local IP address.

    • On Mac, click on the Apple icon in the top-left corner of your desktop and open System Preferences. Then, click on Network. Find your connection type—wifi or Ethernet—and click on it. (It should have a green dot if it’s active.). Look to the right, and you’ll see a sentence that looks like “...is connected to ..... and has the IP address...” The number that follows is your computer’s IP address.

.env and server.js

  • Dotenv configures how our app runs on different environments. (Line 5)

    • For instance, we may want to run on port 3000 on our local computer, but Heroku may want to run on a different port, like 3001 (Line 9 and 1).

    • Square, Stripe, and other payment processors use separate environment domains for testing and production.

    • The call to require() returns an object that has a config() method in it.

  • Because we're instructing the server to run on a different port here, we need to shut down the server and restart it.

    • In the terminal you will have to press Ctrl + C to shut it down, then run "npm start" again.

  • ​Whenever the server.js file changes, the server will need to restart. It won't do that automatically.

    • If you install nodemon ("npm install -g nodemon") you can use "nodemon ." instead of "npm start" and it will automatically restart on changes.

    • The app will be installed globally for all projects. You do not need a nodemon dependency in your package.json file in case if it gets added.

  • If you have a syntax error your app will
    crash automatically and will need to be
    fixed before automatically restarting

'use strict';

// Application Dependencies
const express = require('express');
require('dotenv').config();

// Application Setup 
const app = express();
const PORT = process.env.PORT || 3000;

// Route Definitions

// Route Handlers

// App listener
app.listen(PORT,() => console.log(`Listening on port ${PORT}`));
PORT=3001

server.js

  • We don't have a homepage yet so the browser displays "Cannot GET /". Let's run a .get() function on our app to respond with a simple greeting. (Lines 12, 16-18)

    • This code says when someone requests the "/greet" path of our website we want to execute a callback function that responds with a 200 (OK) status code and send them a simple message.

  • If you add app.use('*', ...), any other requests will be served a 404 (Not found) message. (Line 13, 21-22)

'use strict';

// Application Dependencies
const express = require('express');
require('dotenv').config();

// Application Setup 
const app = express();
const PORT = process.env.PORT || 3000;

// Route Definitions
app.get('/greet', greetHandler);
app.use('*', notFoundHandler);

// Route Handlers
function greetHandler(request, response) {
  response.status(200).send('Hello World!');
}

function notFoundHandler(request, response) {
  response.status(404).json({ notFound: true });
}

// App listener
app.listen(PORT,() => console.log(`Listening on port ${PORT}`));

server.js

  • If you console.log(request) inside a route handler you will see all of the data that is getting sent to our server.

    • request.method will return "GET"

    • request.path will return "/greet"

    • Press Ctrl + L to clear the terminal console.

  • Please note that response.send() will display the message in the browser’s body, while console.log() will display the message in the terminal, not in the browser’s console. Use console.log() to troubleshoot errors and debug.

  • It's common (not required) to direct users to static HTML, CSS, and JavaScript files when they navigate to the root of your website. Those files can be placed inside of a folder called "public". Running a .use() function on our app will respond with those files. (Line 12)

'use strict';

// Application Dependencies
const express = require('express');
require('dotenv').config();

// Application Setup 
const app = express();
const PORT = process.env.PORT || 3000;

// Route Definitions
app.use(express.static('./public'));
app.get('/greet', greetHandler);
app.use('*', notFoundHandler);

// Route Handlers
function greetHandler(request, response) {
  response.status(200).send('Hello World!');
}

function notFoundHandler(request, response) {
  response.status(404).json({ notFound: true });
}

// App listener
app.listen(PORT,() => console.log(`Listening on port ${PORT}`));

server.js

  • Databases and APIs contain vast amounts of data for use on our public website. Servers are the connection between the two.

  • When the server receives the data it will respond to the client with Json.

    • First I assign a new path for data retrieval (Line 14)

    • The response the server receives from the external source will likely be in the form of an array of objects. (Lines 23-34)

    • It will respond to the client with a status code and the data formatted as Json. (Line 35)

      • ​Then in our public folder we can write regular JavaScript files with jQuery that processes ajax requests.

'use strict';

// Application Dependencies
const express = require('express');
require('dotenv').config();

// Application Setup 
const app = express();
const PORT = process.env.PORT || 3000;

// Route Definitions
app.use(express.static('./public'));
app.get('/greet', greetHandler);
app.get('/data', dataHandler);
app.use('*', notFoundHandler);

// Route Handlers
function greetHandler(request, response) {
  response.status(200).send('Hello World!');
}

function dataHandler(request, response) {
  const airplanes = [
    {
      departure: new Date(2020, 7, 24),
      canFly: true,
      pilot: 'Well Trained'
    },
    {
      departure: new Date(2020, 7, 25),
      canFly: false,
      pilot: 'NA'
    }
  ];
  response.status(200).json(airplanes);
}

function notFoundHandler(request, response) {
  response.status(404).json({ notFound: true });
}

// App listener
app.listen(PORT,() => console.log(`Listening on port ${PORT}`));

server.js

  • To account for when things go wrong we can create a route that simulates something bad happened. (Line 15)
    • Visit localhost:3000 and see the Error message. The default behavior when throwing an error is to show everything that went wrong, which isn’t easy to read. In the real world we don’t want to show the user all of that information.
  • After all of our routes we need to add an error middleware and call a function called errorHandler (Line 17)
  • In Express, errorHandlers work by receiving an error and next parameter, in addition to the request and response. (Lines 44-46)
    • The only thing you need to know about "next" is that an error handler won’t work without it.
'use strict';

// Application Dependencies
const express = require('express');
require('dotenv').config();

// Application Setup 
const app = express();
const PORT = process.env.PORT || 3000;

// Route Definitions
app.use(express.static('./public'));
app.get('/greet', greetHandler);
app.get('/data', dataHandler);
app.get('/bad', (request, response) => { throw new Error('Sorry the the inconvenience.'); });
app.use('*', notFoundHandler);
app.use(errorHandler);

// Route Handlers
function greetHandler(request, response) {
  response.status(200).send('Hello World!');
}

function dataHandler(request, response) {
  const airplanes = [
    {
      departure: new Date(2020, 7, 24),
      canFly: true,
      pilot: 'Well Trained'
    },
    {
      departure: new Date(2020, 7, 25),
      canFly: false,
      pilot: 'NA'
    }
  ];
  response.status(200).json(airplanes);
}

function notFoundHandler(request, response) {
  response.status(404).json({ notFound: true });
}

function errorHandler(error, request, response, next) {
  response.status(500).json({ error: true, message: error.message });
}

// App listener
app.listen(PORT,() => console.log(`Listening on port ${PORT}`));

Heroku Review App

  • In server.js remove unnecessary routes (/greet, /data, /bad), and modify how the server responds to root requests (Lines 12, 17-19)
DeltaV Code School Logo
'use strict';

// Application Dependencies
require('dotenv').config();
const express = require('express'); //

// Application Setup
const app = express(); //
const PORT = process.env.PORT || 3000; //

// Route Definitions
app.get('/', rootHandler);
app.use('*', notFoundHandler);
app.use(errorHandler);

// Route Handlers
function rootHandler(request, response) {
  response.status(200).send('City Explorer back-end');
}

function notFoundHandler(request, response) {
  response.status(404).send('404 - Not Found');
}

function errorHandler(error, request, response, next) {
  response.status(500).json({ error: true, message: error.message });
}

// App listener
app.listen(PORT,() => console.log(`Listening on port ${PORT}`)); //

Express Demo

  • Today we will create a front-end app (hosted on GitHub pages) that can talk to our back-end app (hosted on Heroku).

  • We will also set up a location and restaurant API endpoint that uses dummy data. Soon we'll fill that in with real data.

Cors

  • Cors (Cross Origin Resource Sharing) creates middleware that adds useful functionality to our application by allowing connections to be made with outside sources.

  • Add Cors by running "npm install cors"

    • Notice that this adds a new dependency to package.json

  • We will reference cors in the application dependencies (Line 6)

  • After our app is setup, we will use cors. (Line 11)

    • It’s common to forget inner parenthesis

'use strict';

// Application Dependencies
require('dotenv').config();
const express = require('express');
const cors = require('cors');

// Application Setup
const app = express();
const PORT = process.env.PORT;
app.use(cors());

// Route Definitions
app.get('/', rootHandler);
app.use('*', notFoundHandler);
app.use(errorHandler);

// Route Handlers
function rootHandler(request, response) {
  response.status(200).send('City Explorer back-end');
}

function notFoundHandler(request, response) {
  response.status(404).json({ notFound: true });
}

function errorHandler(error, request, response, next) {
  response.status(500).json({ error: true, message: error.message });
}

// App listener
app.listen(PORT,() => console.log(`Listening on port ${PORT}`));

Construct the Data

  • Create an empty array to store constructed Restaurant data. (Line 35)
  • Inside the forEach function, use each object to construct a new Restaurant object and push it to the results array. (Line 37)
  • Create a constructor (Lines 58-64) that has properties that match those in the front-end mustache templates.
    • You will have to inspect the data and determine what properties are needed to populate the newly constructed data. (Lines 59-63).
  • Respond to the front-end with the results array (Line 39).
  • Follow the steps to create a Review App. Then move the changes to Staging and Production as discussed earlier.

'use strict';

// Application Dependencies
require('dotenv').config();
const express = require('express');
const cors = require('cors');

// Application Setup
const app = express();
const PORT = process.env.PORT;
app.use(cors());

// Route Definitions
app.get('/', rootHandler);
app.get('/location', locationHandler);
app.get('/yelp', restaurantHandler);
app.use('*', notFoundHandler);
app.use(errorHandler);

// Route Handlers
function rootHandler(request, response) {
  response.status(200).send('City Explorer back-end');
}

function locationHandler(request, response) {
  const city = request.query.city;
  const locationData = require('./data/location.json');
  const location = new Location(city, locationData);
  response.status(200).send(location);
}

function restaurantHandler(request, response) {
  const restaurantsData = require('./data/restaurants.json');
  const arrayOfRestaurants = restaurantsData.businesses;
  const restaurantsResults = [];
  arrayOfRestaurants.forEach(restaurantObj => {
    restaurantsResults.push(new Restaurant(restaurantObj));
  });
  response.send(restaurantsResults);
}

function notFoundHandler(request, response) {
  response.status(404).json({ notFound: true });
}

function errorHandler(error, request, response, next) {
  response.status(500).json({ error: true, message: error.message });
}

// Constructors
function Location(city, locationData) {
  this.search_query = city;
  this.formatted_query = locationData[0].display_name;
  this.latitude = parseFloat(locationData[0].lat);
  this.longitude = parseFloat(locationData[0].lon);
}

function Restaurant(obj) {
  this.name = obj.restaurant.name;
  this.url = obj.restaurant.url;
  this.rating = obj.restaurant.user_rating.aggregate_rating;
  this.price = obj.restaurant.price_range;
  this.image_url = obj.restaurant.featured_image;
}

// App listener
app.listen(PORT,() => console.log(`Listening on port ${PORT}`));

Client Side Scripting - Week 12 - Node

By Marc Hauschildt

Client Side Scripting - Week 12 - Node

  • 288