Node.js

Week 6

Which version of node is installed?

$ node -v

Node REPL

Read Evaluate Print Loop

$ node
>

Creating a web server

const http = require("http");

//create a server object
http
  .createServer(function(req, res) {
    res.write("Hello World!"); // write a response to the client
    res.end(); // end the response
  })
  .listen(8080);

Running files in node

$ node index.js

http://localhost:8080

http://127.0.0.1:8080

Domain name

IP Address

Debugging files in node

$ node --inspect index.js
Debugger listening on ws://127.0.0.1:9229/…

Breaking it down

const http = require("http");

http.createServer(/* ??? */);

Breaking it down

const http = require("http");

http.createServer(function(request, response) {});

Breaking it down

const http = require("http");

http.createServer(function(req, res) {
  res.write("Hello World! 🤯");
  res.end();
});

Breaking it down

const http = require("http");

http
  .createServer(function(req, res) {
    res.write("Hello World! 🤯");
    res.end();
  })
  .listen(8080);

http://localhost:80

vs

http://localhost:8080

sudo 🧙

$ sudo node index.js

DO NOT DO THIS IN PRODUCTION

Node.js

Single-threaded

Can only do one thing at once

Handles a limited number of users at once

Reverse Proxy

Acts as gateway

Sits in front of one (or more) node.js instances

Handles heavy lifting of encryption, compression, queueing, …

Reverse Proxy

Recap

  • Basics of using node.js
  • Setting up a basic web server
  • sudo
  • Reverse proxies

Express.js

Setup

In a new project:

$ npm init
$ npm install --save express

Basic Example

In index.js:

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

app.listen(8080, function() {
  console.log("Listening on port 8080!");
});
const express = require("express");
const app = express();

app.get("/", function(req, res) {});

app.listen(8080, function() {
  console.log("Listening on port 8080!");
});
const express = require("express");
const app = express();

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

app.listen(8080, function() {
  console.log("Listening on port 8080!");
});

How is that better?

const http = require("http");

http
  .createServer(function(req, res) {
    res.write("Hello World! 🤯");
    res.end();
  })
  .listen(8080);
const express = require("express");
const app = express();

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

app.listen(8080, function() {
  console.log("Listening on port 8080!");
});

Paths in URLs

http://www.constructorlabs.com/course
https://swapi.co/api/people/1/
const http = require("http");

http
  .createServer(function(req, res) {
    if (req.path === "/") {
      res.write("Hello World!");
    } else if (req.path === "/course") {
      res.write("TODO: Course information");
    }
    res.end();
  })
  .listen(8080);
const express = require("express");
const app = express();

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

app.get("/course", function(req, res) {
  res.send("TODO: Course information");
});

app.listen(8080, function() {
  console.log("Listening on port 8080!");
});
const http = require("http");

http
  .createServer(function(req, res) {
    if (req.path === "/") {
      res.write("Hello World!");
    } else if (req.path === "/course") {
      res.write("TODO: Course information");
    } else if (req.path.startsWith("/api/people/")) {
      const personId = req.path.split("/")[3];
      return res.write(`Information about person ${personId}`);
    }
    res.end();
  })
  .listen(8080);
const express = require("express");
const app = express();

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

app.get("/course", function(req, res) {
  res.send("TODO: Course information");
});

app.get("/api/people/:personId", function(req, res) {
  res.send(`Information about person ${personId}`);
});

app.listen(8080, function() {
  console.log("Listening on port 8080!");
});

Routing requests

app.METHOD(PATH, HANDLER);

i.e.

app.get("/", function(req, res) {
  /* do some stuff */
});

HTTP Verbs

Typically,

  • GET: Requesting data from server
  • POST: Supplying data to server
  • PUT, DELETE, and more…

Handling a POST request

app.post("/", function(req, res) {
  res.send("Something was posted! 😲");
});
const express = require("express");
const app = express();

/* … other routes here from before … */

app.post("/", function(req, res) {
  res.send("Something was posted! 😲");
});

app.listen(8080, function() {
  console.log("Listening on port 8080!");
});

Sending a POST request with Postman

Exercise

Create a route that listens for POST requests on path /my-post-route

Create a route that listens for POST requests on path /my-post-route

app.post("/my-post-route", function(req, res) {
  res.send("🔥🔥🔥🔥🔥🔥");
});

Recap

  • Installing and setting up express.js
  • Routing requests with static paths
  • HTTP Verbs
  • Handling POST requests

Handling User Input

Route Parameters

http://www.constructorlabs.com/user/dmitri
https://swapi.co/people/1
https://twitter.com/ConstructorLabs

Query Parameters

http://www.omdbapi.com/?s=BATMAN&apikey=2cda7206

All named route parameters appear in req.params

// /user/jim

app.get("/user/:username", function(req, res) {
  res.send(req.params.username);
});

All query parameters appear in req.query

// /movie-search?searchTerm=BATMAN

app.get("/movie-search", function(req, res) {
  res.send(req.query.searchTerm);
});

Exercise

  1. Create a route that listens for requests on a url such as /city/madrid. Extract the location parameter from the url and send back to the user.
  2. Create a route that listens for requests on a url such as /user/hamzah/photo-id/4536345. Extract the username and photo id parameters from the url and send them back to the user.
  3. Create a new route which reads a colour and size from query in URL and sends them back as response.

A word on security

You cannot trust your users.
app.get("/movie-search", function(req, res) {
  res.send(req.query.searchTerm);
});

But if I send…

GET /movie-search?searchTerm=<script>alert('😈')</script>

…I get back…

<script>
  alert("😈");
</script>

Sending JSON

app.get('/playlist', function(req, res){
  res.json({ playlist: ['Iron Maiden', 'Guns N\' Roses', 'Black Sabbath'] });
});

Receiving JSON

First, we need body-parser:

$ npm install --save body-parser

Receiving JSON

const express = require("express");
const bodyParser = require("body-parser"); // Import body-parser
const app = express();

app.use(bodyParser.json()); // parse incoming json

app.post("/inbound", function(req, res) {
  res.json(req.body);
});

Set Content-type header to application/json:

Set the body of the request to your JSON:

try / catch

try {
  doSomething();
} catch (error) {
  doSomethingElse();
}
app.get("/musicians", function(req, res) {
  try {
    const musicians = getMusiciansFromNonExistentDatabase();
    res.json(musicians);
  } catch (error) {
    res.status(500).json({ error: "Could not connect to database" });
  }
});

Exercise

Follow the previous examples (see the syllabus) to send and receive JSON to/from your server using Postman.

Handlebars

<div class="entry">
  <h1>{{title}}</h1>
  <div class="body">
    {{body}}
  </div>
</div>
// template
<div class="entry">
  <h1>{{title}}</h1>
  <div class="body">
    {{body}}
  </div>
</div>
// data
{
    title: "Hello",
    body: "World"
}

// output
<div class="entry">
  <h1>Hello</h1>
  <div class="body">
    World
  </div>
</div>
// template
<div class="entry">
  <h1>{{title}}</h1>
  <div class="body">
    {{{body}}}
  </div>
</div>
// data
{
    title: "Hello",
    body: "<h2>Oh look, a list</h2><ul><li>Item 1</li><li>Item 2</li></ul>"
}

// output
<div class="entry">
  <h1>Hello</h1>
  <div class="body">
    <h2>Oh look, a list</h2>
    <ul>
      <li>Item 1</li>
      <li>Item 2</li>
    </ul>
  </div>
</div>

Using Handlebars with Express

$ npm install hbs --save

Set express's "view engine" to handlebars in ./index.js:

app.set("view engine", "hbs");

Our first template

In ./views/hello.hbs:

<!DOCTYPE html>
<html>
    <head>
        <title>{{title}}</title>
    </head>
    <body>
        <h1>{{heading}}</h1>
        <p>{{message}}</p>
    </body>
</html>

Using our template in a route

In ./index.js:

app.post("/hello", function(req, res) {
  res.render("hello", req.body);
});

Exercise

  1. Create a template using Handlebars
  2. Create a new route to render that template with some data
  3. Call your new route using Postman

Calling External APIs

Install node-fetch:

$ npm install --save node-fetch

At the top of ./index.js:

const fetch = require("node-fetch");

/* Everything else as before */

app.get("/movie", function(req, res) {
  fetch("http://www.omdbapi.com/?s=batman&apikey=INSERT_API_KEY_HERE")
    .then(function(response) {
      return response.json();
    })
    .then(function(json) {
      res.json(json);
    })
    .catch(function(error) {
      res.status(500).json({ error: "We failed to fetch the movie" });
    });
});

Exercise

  1. Update the route above to receive a search query so we can search for any movie title, not just 'batman'
  2. Add pagination to above route definition so we can access any results page from our search via our own API
  3. Create a new route called /average which will do a search, calculate the average year made of returned movies and add the average property to the results object we return.
  4. Fetch a movie using the t param which returns movie details. Create a template and display the movie details with HTML

Stretch goal

Create a /sorted-movies route which will return a search result where the movies on each page are sorted by year