├── .gitignore ├── README.md ├── day1 ├── README.md ├── exercises │ ├── README.md │ ├── evennumbers.js │ ├── freq.js │ ├── jokes.js │ ├── tags.txt │ ├── wikisearch.js │ └── zlatan-server.js └── resources │ ├── cmd.js │ ├── files-async.js │ ├── files.js │ ├── hello.js │ ├── http-get-today.js │ └── server.js └── day2 ├── README.md └── exercises ├── forms ├── app.js ├── package.json └── public │ ├── search.html │ └── subscribe.html ├── hello └── app.js ├── mid-static ├── app.js ├── package.json └── public │ ├── image1.jpg │ └── index.html └── products-api ├── app.js ├── package.json └── public ├── client.js └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node.js Tutorial 2 | 3 | Node.js is a server-side platform built on Google Chrome's JavaScript Engine (V8 Engine). Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices. 4 | 5 | We are not going to cover the all the features, only the basics. If you want to know more about how Node.js works, checkout some videos on youtube: 6 | 7 | [![](http://img.youtube.com/vi/jOupHNvDIq8/0.jpg)](http://www.youtube.com/watch?v=jOupHNvDIq8 "") 8 | 9 | 10 | In this hands-on tutorial, the goal is to give you some pointers and tools so you can start building your own node.js applications. 11 | 12 | 13 | ## Installation 14 | You can install Node.js by following the instructions from the Node.js project webpage ([https://nodejs.org/en/](https://nodejs.org/en/)). 15 | 16 | If you're using a package manager in your OS, you might find ports already available. For example: 17 | - [Installing nodejs using MacPorts](https://jonlabelle.com/snippets/view/shell/install-nodejs-macport). 18 | - [Installing nodejs in Ubuntu](https://websiteforstudents.com/install-the-latest-node-js-and-nmp-packages-on-ubuntu-16-04-18-04-lts/) 19 | - If you're using anything else, you probably know what you're doing :) 20 | 21 | ## Materials 22 | You'll find the materials for the lectures in the day* folders. 23 | 24 | 25 | -------------------------------------------------------------------------------- /day1/README.md: -------------------------------------------------------------------------------- 1 | # Node.js Tutorial 2 | We start this tutorial by looking at the basics of node.js. 3 | 4 | 5 | ## 1. Basic scripting 6 | 7 | ### Hello world! 8 | 9 | Let's open our editor and create a file named *hello.js* 10 | 11 | ```javascript 12 | /* Hello World! program in Node.js */ 13 | console.log("Hello World!"); 14 | ``` 15 | 16 | Running the script 17 | ```shell 18 | $ node hello.js 19 | ``` 20 | 21 | As you can see, we are simply echo-ing the contents in the console. This is the exact equivalent of what we would see in the browser console. Indeed, you can access the same type of interactive console by simply typing *node* in your terminal. For example: 22 | 23 | ```shell 24 | $ node 25 | > 1+1 26 | 2 27 | > var a = 2 28 | undefined 29 | > a 30 | 2 31 | 32 | ``` 33 | 34 | ### Command line parameters 35 | 36 | Often you need to access to command line parameters. For example, 37 | 38 | ```shell 39 | $ node evennumbers.js 40 | ``` 41 | 42 | In node.js you do this by accessing *process*, which is a global variable containing informationan about the current node.js process. For example: 43 | 44 | ```javascript 45 | for (var i = 0; i < process.argv.length; i++) { 46 | console.log(i + ' -> ' + (process.argv[i])); 47 | } 48 | ``` 49 | 50 | ### Accessing the file system 51 | 52 | Another useful feature is accessing the file system. Let's start right away from an example: 53 | 54 | ```javascript 55 | // Loading the file system library 56 | var fs = require("fs"); 57 | 58 | // File name from the common line params 59 | var fileName = process.argv[2]; 60 | 61 | // Accessing the content of the file synchnously 62 | var data = fs.readFileSync(fileName, "utf8"); 63 | console.log(data); 64 | 65 | console.log("Program ended."); 66 | 67 | ``` 68 | 69 | Now try running the code 70 | 71 | ```shell 72 | node files.js path/to/file 73 | ``` 74 | 75 | There are two things to highlight in the above code. 76 | 77 | #### a. Loading libraries 78 | To access the file system we need to load the File System module. This module comes with the standard Node.js installation, so we do not need to install any third-party libraries (We'll get to that later in this tutorial). 79 | 80 | ```javascript 81 | var fs = require("fs"); 82 | ``` 83 | 84 | The require instruction above loads the module "fs" and assigns an instance to the variable fs. Through this instance then we can have access to all the funcions exported by that module. 85 | 86 | For more on how require works, [check this interesting blog post](http://fredkschott.com/post/2014/06/require-and-the-module-system/). 87 | 88 | #### b. Blocking / synchronous call 89 | As you might have noticed, the following operations are exececuted in sequence, meaning that you see the contents of the file and then the "Program ended." message. 90 | 91 | ```javascript 92 | var data = fs.readFileSync(fileName, "utf8"); 93 | console.log(data); 94 | 95 | console.log("Program ended."); 96 | ``` 97 | This happens because the *readFileSync* functions is a synchronous implementation, that make the process to wait until the funcion finished its operation to continue. However, this is typically an undesired feature, and as you will see Node.js is built around the concept of non-blocking / asynchonous calls. 98 | 99 | Why do you think this un undesired feature. Can you think of any instances where you'd need a non-blocking implementation? 100 | 101 | ### Non-blocking calls 102 | Let's try now an alternative implementation of our files script, the *files-async.js*. 103 | 104 | ```javascript 105 | // Loading the file system library 106 | var fs = require("fs"); 107 | 108 | // File name from the common line params 109 | var fileName = process.argv[2]; 110 | 111 | // Accessing the content of the file asynchnously 112 | fs.readFile(fileName, "utf8", function(error, data) { 113 | console.log(data); 114 | }); 115 | 116 | console.log("Program ended."); 117 | ``` 118 | 119 | What is the difference now?. 120 | 121 | 122 | In this case readFile expects a *callback* function. These are very common in javascript libraries, and is the function that will be called when the method invoked realises its purpose (which can be one or multiple times). In this specific case, we also have an anonymous function, meaning a function that was declared on the spot (declared at runtime) and that unlike typical functions, it does not have a name. 123 | 124 | Later in the tutorial we'll have a look at *Promises*, which are a nicer looking alternatives to callback functions for asynchnous calls. 125 | 126 | ## Exercises 127 | Let's take 10 minutes to work on the following exercises. We do not expect you to finish both right now, so do as much as you can and leave the rest as homework. 128 | 129 | 1. **Implement evennumbers.js**, verying that the user provides the right number of parameters (2) and in the right format (numbers). 130 | 131 | 2. **Create a frequency table** from an input file containing on word per line. 132 | Example Input: 133 | ```shell 134 | $ cat tags.txt 135 | Dog 136 | Cat 137 | Dog 138 | Dog 139 | ``` 140 | Example output: 141 | ```shell 142 | $ node freq.js tags.txt 143 | Tag Frequency 144 | Dog 3 145 | Cat 1 146 | ``` 147 | 148 | ## 2. Interacting with online services 149 | As we already know, this is how a typical http server interacts with a client. 150 | 151 | [![Source: Wikipedia](https://upload.wikimedia.org/wikipedia/commons/b/bc/HTTP_cookie_exchange.svg)](https://commons.wikimedia.org/wiki/File:HTTP_cookie_exchange.svg "") 152 | 153 | In our case, the client is actually a script and not really a web browser. We can invoke service calls using the standard *http* and *https* modules that come with the standard Node.js installation. 154 | 155 | Run the script *http-get-today.js*, which requests a service that tells us what happened today in history. 156 | 157 | ```javascript 158 | var https = require('https'); 159 | 160 | var url = "https://history.muffinlabs.com/date"; 161 | 162 | https.get(url, function(resp) { 163 | var data = ""; 164 | 165 | // We receive the response data in a stream, so here 166 | // we specify what to do with each chunk we receive 167 | resp.on("data", function(chunk) { 168 | data += chunk; 169 | }); 170 | 171 | // We specify what to do when we finish receiving the 172 | // stream of data. 173 | resp.on("end", function() { 174 | // We receive the content as "text" and print it 175 | console.log(data); 176 | }); 177 | 178 | }).on("error", function(err) { 179 | console.log("Error: " + err.message); 180 | }); 181 | ``` 182 | 183 | The function https.get works by specifying a url and a callback function. It works at very low level, informing us of every single chunk in the response in a stream of data, thus requiring us to put together the response body. 184 | 185 | 186 | ```javascript 187 | resp.on("data", function(chunk) { 188 | data += chunk; 189 | }); 190 | ``` 191 | 192 | Then when we finish receiving the data, we can process process the information. 193 | The output of the service is in JSON format. What is JSON? (source: [w3school](https://www.w3schools.com/js/js_json_intro.asp)) 194 | - JSON: stands for: **J**ava**S**cript **O**bject **N**otation. 195 | - JSON is a syntax for storing and exchanging data. 196 | - JSON is text, written with JavaScript object notation. 197 | 198 | ```json 199 | { 200 | "date":"September 11", 201 | "url":"https://wikipedia.org/wiki/September_11", 202 | "data":{ 203 | "Events":[ ], 204 | "Births":[ ], 205 | "Deaths":[ ] 206 | } 207 | } 208 | ``` 209 | 210 | Since server / client exchange data in "text" format, we need to transform to a Javascript object. This is very straightforward with JSON, since the content is essentially in javascript object notation. 211 | Let's modify the "end" callback function: 212 | 213 | ```javascript 214 | resp.on("end", function() { 215 | // We receive the content as "text" and print it 216 | var obj = JSON.parse(data) 217 | console.log(obj.date); 218 | }); 219 | ``` 220 | 221 | The *http* and *https* modules are powerful, but certainly not simplest ones out there. There are many ways in which you can perform http / https calls. [This great blog post summarises](https://www.twilio.com/blog/2017/08/http-requests-in-node-js.html) five different ways you can do this. 222 | 223 | ## Exercises 224 | 225 | 3. **Random joke**: Implement a script *jokes.js* that returns a random joke from the *The Internet Chuck Norris database*. 226 | The documentation specifies: 227 | 228 | ``` 229 | // To get a random joke, invoke: 230 | http://api.icndb.com/jokes/random 231 | 232 | // If you want to change the name of "Chuck Norris", 233 | // specify the firstName and lastName parameters: 234 | http://api.icndb.com/jokes/random?firstName=John&lastName=Doe 235 | 236 | ``` 237 | 238 | 4. **Wiki pages**: Implement a script that given an input term, search for the (top 5) matching Wiki pages. For example: 239 | 240 | ```shell 241 | $ node wikisearch.js "Albert Einstein" 242 | 243 | Albert Einstein 244 | Hans Albert Einstein 245 | Outline of Albert Einstein 246 | Albert Einstein's brain 247 | Einstein family 248 | 249 | ``` 250 | 251 | The documentation of the Wikipedia API suggests the following call: 252 | ``` 253 | https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=Albert%20Einstein&format=json 254 | 255 | ``` 256 | which has the following output format. You elements to display are in the query.search array. 257 | ```json 258 | { 259 | "batchcomplete":"", 260 | "continue":{ 261 | "sroffset":10, 262 | "continue":"-||" 263 | }, 264 | "query":{ 265 | "searchinfo":{ 266 | "totalhits":5166 267 | }, 268 | "search":[ 269 | { 270 | "ns":0, 271 | "title":"Albert Einstein" 272 | }, 273 | ] 274 | } 275 | } 276 | ``` 277 | 278 | ## 3. Creating a node server 279 | 280 | What if we want to create our own server? 281 | Creating a server that can handle requests from a client is very simple with node, and we can do it with the same standard http module. 282 | 283 | Let's try our *server.js* script. 284 | 285 | ```javascript 286 | var http = require('http'); 287 | var port = 3000; 288 | 289 | var requestHandler = function(request, response) { 290 | console.log(request.url); 291 | response.end('Hello World!'); 292 | } 293 | 294 | var server = http.createServer(requestHandler); 295 | server.listen(port); 296 | 297 | ``` 298 | 299 | Is it running? Now then let's open [http://localhost:3000](http://localhost:3000) in a browser (`ctrl+c` in the terminal to end the execution). 300 | Let's try to inspect the contents of the request.headers. 301 | 302 | We won't go into detail into this way of creating a node server, as it is a bit low level. In the next class we'll dig into a popular web framework called Express.js. 303 | 304 | ## Challenge: 305 | Can you create a service that tells Zlatan jokes? You can use a format similar to that of the Internet Chuck Norris Database. You can store your jokes in an array, in a file, or simply reuse the Chuck Norris Service. 306 | 307 | 308 | ## References & further reading 309 | - https://nodejs.org/en/docs/guides/ 310 | - https://nodejs.org/en/docs/guides/anatomy-of-an-http-transaction/ 311 | 312 | 313 | -------------------------------------------------------------------------------- /day1/exercises/README.md: -------------------------------------------------------------------------------- 1 | # Exercises 2 | Let's take 10 minutes to work on the following exercises. We do not expect you to finish both right now, so do as much as you can and leave the rest as homework. 3 | 4 | 1. **Implement evennumbers.js**, verying that the user provides the right number of parameters (2) and in the right format (numbers). 5 | 6 | 2. **Create a frequency table** from an input file containing on word per line. 7 | Example Input: 8 | ```shell 9 | $ cat tags.txt 10 | Dog 11 | Cat 12 | Dog 13 | Dog 14 | ``` 15 | Example output: 16 | ```shell 17 | $ node freq.js tags.txt 18 | Tag Frequency 19 | Dog 3 20 | Cat 1 21 | ``` 22 | 23 | Tips: 24 | - Try to split the file contents by the newline character (\n) 25 | - Use the readline module ([suggestions from StackOverflow](https://stackoverflow.com/questions/6156501/read-a-file-one-line-at-a-time-in-node-js)) 26 | 27 | 28 | 3. **Random joke**: Implement a script *jokes.js* that returns a random joke from the *The Internet Chuck Norris database*. 29 | The documentation specifies: 30 | 31 | ``` 32 | // To get a random joke, invoke: 33 | http://api.icndb.com/jokes/random 34 | 35 | // If you want to change the name of "Chuck Norris", 36 | // specify the firstName and lastName parameters: 37 | http://api.icndb.com/jokes/random?firstName=John&lastName=Doe 38 | 39 | ``` 40 | 41 | 4. **Wiki pages**: Implement a script that given an input term, search for the (top 5) matching Wiki pages. For example: 42 | 43 | ```shell 44 | $ node wikisearch.js "Albert Einstein" 45 | 46 | Albert Einstein 47 | Hans Albert Einstein 48 | Outline of Albert Einstein 49 | Albert Einstein's brain 50 | Einstein family 51 | 52 | ``` 53 | 54 | The documentation of the Wikipedia API suggests the following call: 55 | ``` 56 | https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=Albert%20Einstein&format=json 57 | 58 | ``` 59 | which has the following output format. You elements to display are in the query.search array. 60 | ```json 61 | { 62 | "batchcomplete":"", 63 | "continue":{ 64 | "sroffset":10, 65 | "continue":"-||" 66 | }, 67 | "query":{ 68 | "searchinfo":{ 69 | "totalhits":5166 70 | }, 71 | "search":[ 72 | { 73 | "ns":0, 74 | "title":"Albert Einstein" 75 | }, 76 | ] 77 | } 78 | } 79 | ``` 80 | 81 | 82 | ## Challenge: 83 | Can you create a service that tells Zlatan jokes? You can use a format similar to that of the Internet Chuck Norris Database. You can store your jokes in an array, in a file, or simply reuse the Chuck Norris Service. 84 | 85 | -------------------------------------------------------------------------------- /day1/exercises/evennumbers.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /day1/exercises/freq.js: -------------------------------------------------------------------------------- 1 | /* Implement your solution here */ 2 | -------------------------------------------------------------------------------- /day1/exercises/jokes.js: -------------------------------------------------------------------------------- 1 | /* Implement your solution here */ 2 | -------------------------------------------------------------------------------- /day1/exercises/tags.txt: -------------------------------------------------------------------------------- 1 | Cat 2 | Dog 3 | Horse 4 | Cat 5 | Dog 6 | Rabbit 7 | Dog 8 | Cat 9 | Cat 10 | Rabbit 11 | Mule 12 | Cow 13 | Duck 14 | Horse 15 | Cat 16 | Mule 17 | Rabbit 18 | Lion 19 | Elephant 20 | Cat 21 | Dog 22 | Rabbit 23 | Cow 24 | Duck -------------------------------------------------------------------------------- /day1/exercises/wikisearch.js: -------------------------------------------------------------------------------- 1 | /* Implement your solution here */ 2 | -------------------------------------------------------------------------------- /day1/exercises/zlatan-server.js: -------------------------------------------------------------------------------- 1 | /* Implement your solution here */ -------------------------------------------------------------------------------- /day1/resources/cmd.js: -------------------------------------------------------------------------------- 1 | /* 2 | * cmd.js 3 | * In this example we see how to access commmand 4 | * line arguments. 5 | */ 6 | for (var i = 0; i < process.argv.length; i++) { 7 | console.log(i + ' -> ' + (process.argv[i])); 8 | } -------------------------------------------------------------------------------- /day1/resources/files-async.js: -------------------------------------------------------------------------------- 1 | /* 2 | * files-async.js 3 | * Example of asynchronous access to files 4 | */ 5 | 6 | // Loading the file system library 7 | var fs = require("fs"); 8 | 9 | // File name from the common line params 10 | var fileName = process.argv[2]; 11 | 12 | // Accessing the content of the file asynchnously 13 | fs.readFile(fileName, "utf8", function(error, data) { 14 | console.log(data); 15 | }); 16 | 17 | console.log("Program ended."); -------------------------------------------------------------------------------- /day1/resources/files.js: -------------------------------------------------------------------------------- 1 | /* 2 | * files.js 3 | * Example of synchronous access to files 4 | */ 5 | 6 | // Loading the file system library 7 | var fs = require("fs"); 8 | 9 | // File name from the common line params 10 | var fileName = process.argv[2]; 11 | 12 | // Accessing the content of the file synchnously 13 | var data = fs.readFileSync(fileName, "utf8"); 14 | console.log(data); 15 | 16 | console.log("Program ended."); -------------------------------------------------------------------------------- /day1/resources/hello.js: -------------------------------------------------------------------------------- 1 | /* Hello World! program in Node.js */ 2 | console.log("Hello World!"); -------------------------------------------------------------------------------- /day1/resources/http-get-today.js: -------------------------------------------------------------------------------- 1 | /* 2 | * http-get-today.js 3 | * In this example we see how to perform http(s) get requests 4 | * using the standard http(s) library. 5 | */ 6 | 7 | var https = require('https'); 8 | 9 | var url = "https://history.muffinlabs.com/date"; 10 | 11 | https.get(url, function(resp) { 12 | var data = ""; 13 | 14 | // We receive the response data in a stream, so here 15 | // we specify what to do with each chunk we receive 16 | resp.on("data", function(chunk) { 17 | data += chunk; 18 | }); 19 | 20 | // We specify what to do when we finish receiving the 21 | // stream of data. 22 | resp.on("end", function() { 23 | // We receive the content as "text" and print it 24 | console.log(data); 25 | }); 26 | 27 | }).on("error", function(err) { 28 | console.log("Error: " + err.message); 29 | }); -------------------------------------------------------------------------------- /day1/resources/server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * server.js 3 | * In this example we see how to create a simple server. 4 | */ 5 | var http = require('http'); 6 | var port = 3000; 7 | 8 | var requestHandler = function(request, response) { 9 | console.log(request.url); 10 | response.end('Hello World!'); 11 | } 12 | 13 | var server = http.createServer(requestHandler); 14 | server.listen(port); 15 | console.log("Server started, listening on port", port); 16 | -------------------------------------------------------------------------------- /day2/README.md: -------------------------------------------------------------------------------- 1 | # Node.js Tutorial 2 | In this part of the tutorial, we focus on package management and using Express to develop a backend server exposing a REST API. 3 | 4 | 5 | ## Package mangement with npm 6 | NPM is a very powerful tool that can help you manage project dependencies and in general automate development workflows, much like `ant` or `make` in java and C. 7 | 8 | In the exercises folder we have a project called *hello*, which uses an external module: `express`. How do we manage this dependency? 9 | 10 | ### Package.json 11 | 12 | The file `package.json` contains the metadata regarding your project, including name, version, license, and dependencies. Although you can install dependencies without a `package.json` file, it is the best way to keep track of your local dependencies. 13 | 14 | 15 | How do we start? We execute the command below and follow the instructions prompted. 16 | 17 | ```shell 18 | npm init 19 | ``` 20 | This generates the `package.json` file containing with a structure similar to this one: 21 | 22 | ```json 23 | { 24 | "name": "hello", 25 | "version": "1.0.0", 26 | "description": "Cool package", 27 | "main": "app.js", 28 | "scripts": { 29 | "test": "echo \"Error: no test specified\" && exit 1" 30 | }, 31 | "author": "Marcos", 32 | "license": "ISC" 33 | } 34 | 35 | ``` 36 | ### Installing modules 37 | 38 | To install an external module, we can use the `npm install` command 39 | 40 | ```shell 41 | npm install --save express 42 | ``` 43 | 44 | The save params indicates npm to add the module to the list of dependencies in the `package.json` file. Indeed, if you check its contents, you'll now see: 45 | 46 | ```json 47 | { 48 | "name": "hello", 49 | "version": "1.0.0", 50 | "description": "Cool package", 51 | "main": "app.js", 52 | "scripts": { 53 | "test": "echo \"Error: no test specified\" && exit 1" 54 | }, 55 | "author": "Marcos", 56 | "license": "ISC", 57 | "dependencies": { 58 | "express": "^4.16.3" 59 | } 60 | } 61 | ``` 62 | 63 | ### Installing all dependencies from a project 64 | 65 | When someone shares the source code of their project (on a github, other source code management system, but even on a memory stick), they will not put their local dependency builds with their source code but give you only the `package.json` dependecies. 66 | 67 | Let us "uninstall" express for a second, using `npm uninstall express` (if you add --save you'll also remove it from `package.json` but that's not what we want in this case). This removes the module from our project, and put it at the state you'll find any project on github. The way you install the dependencies of the project is then with the following command. 68 | 69 | ```shell 70 | npm install 71 | ``` 72 | 73 | ### Scripts and more 74 | You can do so much more with npm, but here we are covering only the basics. A very useful feature you want to look into is `scripts`, which help you automate some tasks. Those interested can check the further reading section. 75 | 76 | 77 | ## Express 78 | 79 | Web framework for Node.js. 80 | *Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.* (Source: https://expressjs.com/). 81 | 82 | Let's rewrite our node server based on the `http` module using express: 83 | 84 | 85 | ### Hello World! 86 | 87 | ```javascript 88 | var express = require('express'); 89 | var app = express(); 90 | 91 | var port = 3000; 92 | 93 | // Handling GET requests 94 | app.get('/', function(req, res){ 95 | res.send('Hello World!'); 96 | }); 97 | 98 | app.listen(port, function() { 99 | console.log('Server running on port ', port); 100 | }); 101 | ``` 102 | 103 | There are a few interesting concepts that we can highlight in this trivial example: 104 | - we can listen to specific http verbs (`app.get`) 105 | - we can specify specific routes (`/`) 106 | 107 | The above help us focus on the services that we want to implement, without worriying about 108 | the logic for handling the request (e.g., checking manually that the request method is GET, and that the request url is '/'). 109 | 110 | 111 | ### Serving static files 112 | 113 | If we had to implement a way to serve static files, one way would be to: 114 | - Check the request URL 115 | - Look for the file in the local file system 116 | - Check the type / format, and set the headers manually 117 | 118 | This requires quite some work, fortunately express provides some standard way of managing common features like this one. Look at the example `mid-static`. 119 | 120 | ```javascript 121 | var express = require('express'); 122 | var app = express(); 123 | 124 | var port = 3000; 125 | 126 | app.use(express.static('public')); 127 | 128 | // Handling GET requests 129 | app.get('/hello', function(req, res){ 130 | res.send('Hello World!'); 131 | }); 132 | 133 | app.listen(port, function() { 134 | console.log('Server running on port ', port); 135 | }); 136 | ``` 137 | 138 | What the above does is to mount the built-in `static` middleware, which facilitates the task of servicing static assets. 139 | 140 | Run the script and then open [http://localhost:3000](http://localhost:3000) in your browser. What happens when you request the following?: 141 | - [http://localhost:3000/hello](http://localhost:3000/hello) 142 | - [http://localhost:3000/index.html](http://localhost:3000/index.html) 143 | - [http://localhost:3000/image1.jpg](http://localhost:3000/image1.jpg) 144 | 145 | You can decide where path in which the static files will be access, by simply specifying the root as first parameter in `app.use`: 146 | 147 | ```javascript 148 | app.use('/static', express.static('public')); 149 | ``` 150 | 151 | But what are middlewares, and how do they work?. Let's look at the following informative figure by [hannahhoward](https://github.com/hannahhoward): 152 | 153 | [![](https://camo.githubusercontent.com/af25dcefb2d951a9925adfc0c2c11f9684e19c1e/687474703a2f2f61647269616e6d656a69612e636f6d2f696d616765732f657870726573732d6d6964646c6577617265732e706e67)](https://gist.github.com/hannahhoward/fe639ca2f6e95eaf0ede34a218e948f9 "") 154 | 155 | 156 | ### Handling requests from a browser 157 | Serving requests to web forms can be done easily by extending our previous example in the following way (source code in `exercises/forms`): 158 | 159 | 160 | ```javascript 161 | var express = require('express'); 162 | var app = express(); 163 | 164 | // Loading utils to inspect the content of js objects 165 | var util = require('util'); 166 | 167 | var port = 3000; 168 | 169 | app.use('/', express.static('public')); 170 | 171 | // Handling GET requests 172 | app.get('/search', function(req, res){ 173 | 174 | console.log(util.inspect(req.headers, {showHidden: false, depth: null})) 175 | console.log(util.inspect(req.url, {showHidden: false, depth: null})) 176 | console.log(util.inspect(req.query, {showHidden: false, depth: null})) 177 | 178 | res.status(200).send('These are the items found!'); 179 | }); 180 | 181 | app.post('/subscribe', function(req, res){ 182 | 183 | console.log(util.inspect(req.headers, {showHidden: false, depth: null})) 184 | console.log(util.inspect(req.params, {showHidden: false, depth: null})) 185 | 186 | res.status(201).send('You are now subscribed!'); 187 | 188 | }); 189 | 190 | app.listen(port, function() { 191 | console.log('Server running on port ', port); 192 | }); 193 | ``` 194 | Run the script of this skeleton. 195 | 196 | In the code you can see that we are listening to two different routes `subscribe` and `search`. Both rely on different HTTP verbs (post and get), and are built to serve the needs of two different types of requests. Let's open the example clients: 197 | 198 | - http://localhost:3000/search.html 199 | - http://localhost:3000/subscribe.html 200 | 201 | Play with the above forms, submit some example requests and analyse what arrives to the server. Some points to discuss: 202 | 203 | 1. What do you think is the reason behind using GET / POST ? 204 | 2. Where is the data we are sending? 205 | 206 | On the first point, there is nice an extensive discussion here (https://www.w3schools.com/tags/ref_httpmethods.asp). Apart from some obvious practical reasons, we'll discuss some more fundamentals one when we get to REST APIs. 207 | 208 | On the second point, an alternative would be to process the put together the response body by concatenating chunks from the stream (remember when we did this in the first day?) but that is not necessary, because the body-parser middleware provides this funcionality already. 209 | 210 | **Parsing request body contents** 211 | 212 | First, we install the library 213 | 214 | ```shell 215 | npm install --save body-parser 216 | ``` 217 | and then add the following code to our forms app.js 218 | 219 | ```javascript 220 | // Load the module 221 | var bodyParser = require('body-parser'); 222 | 223 | // Mount body-parser middleware, and instruct it to 224 | // process form url-encoded data 225 | app.use(bodyParser.urlencoded()); 226 | 227 | ``` 228 | After doing this, we should be able to access the form data by directy using `req.body` 229 | 230 | ```javascript 231 | console.log(req.body); 232 | ``` 233 | 234 | Notice that in this case we were parsing form data, but depending on the type of data 235 | you want your service to handle, you'll need a different type of parsing. This post provides 236 | a nice overview: 237 | 238 | https://www.quora.com/What-exactly-does-body-parser-do-with-express-js-and-why-do-I-need-it 239 | 240 | - bodyParser.raw(): Doesn't actually parse the body, but just exposes the buffered up contents from before in a Buffer on req.body. 241 | - bodyParser.text(): Reads the buffer as plain text and exposes the resulting string on req.body. 242 | - bodyParser.urlencoded(): Parses the text as URL encoded data (which is how browsers tend to send form data from regular forms set to POST) and exposes the resulting object (containing the keys and values) on req.body. For comparison; in PHP all of this is automatically done and exposed in $_POST. 243 | - bodyParser.json(): Parses the text as JSON and exposes the resulting object on req.body. 244 | 245 | **What about HTTP status codes?** 246 | You can read the following blog post, which summarises nicely what each stands for and when to use them: 247 | https://www.digitalocean.com/community/tutorials/how-to-troubleshoot-common-http-error-codes 248 | 249 | - 1xx: Informational 250 | - 2xx: Success 251 | - 3xx: Redirection 252 | - 4xx: Client Error 253 | - 5xx: Server Error 254 | 255 | 256 | ### Exercises 257 | 1. Finish implementing the subcription service, using the array of people as "database" 258 | 2. Finish implementing the search functionality, looking for subscribers in the people array. 259 | 260 | 261 | ## RESTful APIs 262 | *Representational State Transfer (REST) is an architectural style that defines a set of constraints to be used for creating web services. Web Services that conform to the REST architectural style, or RESTful web services, provide interoperability between computer systems on the Internet. **REST-compliant web services allow the requesting systems to access and manipulate textual representations of web resources by using a uniform and predefined set of stateless operations.*** (Source [Wikipedia](https://en.wikipedia.org/wiki/Representational_state_transfer)) 263 | 264 | * web resource: any resource on the web that can be identied by an URI (universal resource identifier - urls are the most common type of identifiers). 265 | * text representation: json, xml, ... 266 | * operations: In our case we are talking about HTTP operations (GET, POST, PUT, DELETE) 267 | 268 | ### Managing a web resource 269 | 270 | For example, let's say we want to implement a REST API to manage products. 271 | ```json 272 | { 273 | "id" : 1, 274 | "name" : "iPhone XL", 275 | "description" : "Extra large" 276 | } 277 | ``` 278 | 279 | We then map CRUD (or CRUSD) operations to the standard HTTP verbs. 280 | 281 | 282 | | Operation | HTTP Verb | URI | Req body | Resp body | 283 | |-----------|--------------|----------------|-------------|------------| 284 | | Search | GET | /products | Empty | [Product+] | 285 | | Create | POST | /products | Product | Empty | 286 | | Read | GET | /products/:id | Empty | Product | 287 | | Update | PUT / PATCH | /products/:id | Product* | Product | 288 | | Delete | DELETE | /products/:id | Empty | Empty | 289 | 290 | 291 | This works pretty well with simple resources. More complex APIs will require special attention to the relationship between web resources, and ways of traversing the relationships. For example, to get the list of products associated to a user (`/user/:id/products`). 292 | 293 | Our challenge: Implementing the products API!. There is a stub implementation in `exercises/products-api`, which includes a simple client web app. 294 | 295 | 296 | ### Exercises 297 | 1. Finish implementing the Products API backend 298 | 2. Implement the web API for managing student registrations as specified here: https://www.studytonight.com/rest-web-service/designing-the-rest-api 299 | 300 | ### Challenge 301 | Create the Zlatan service, replicating the functionality of the Chuck Norris Internet Database, and using Google Spreadsheet as you database. Tip: You can use the link a downloaded CSV file (File -> Download as -> Comma Separated Values) and transform it to json. 302 | 303 | 304 | ## References and further reading 305 | 306 | - https://nodesource.com/blog/an-absolute-beginners-guide-to-using-npm/ 307 | - https://docs.npmjs.com/misc/scripts 308 | - https://ourcodeworld.com/articles/read/261/how-to-create-an-http-server-with-express-in-node-js 309 | 310 | -------------------------------------------------------------------------------- /day2/exercises/forms/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * app.js 3 | * Main entry point of the forms project 4 | * This script shows how to create a server 5 | * that can handle request from web forms 6 | */ 7 | var express = require('express'); 8 | var app = express(); 9 | 10 | // Loading utils to inspect the content of js objects 11 | var util = require('util'); 12 | 13 | var port = 3000; 14 | 15 | app.use('/', express.static('public')); 16 | 17 | var people = [{ name : "Mario Ferrari", email : "fake@news.it"}, 18 | { name : "Carlo Smith", email : "youreach@menot.it"}, 19 | { name : "Fabio Ferrari", email : "email@email.com"}]; 20 | 21 | // Handling GET requests 22 | app.get('/search', function(req, res){ 23 | 24 | console.log(util.inspect(req.headers, {showHidden: false, depth: null})) 25 | console.log(util.inspect(req.url, {showHidden: false, depth: null})) 26 | console.log(util.inspect(req.query, {showHidden: false, depth: null})) 27 | 28 | res.status(200).send('These are the items found!'); 29 | }); 30 | 31 | app.post('/subscribe', function(req, res){ 32 | 33 | console.log(util.inspect(req.headers, {showHidden: false, depth: null})) 34 | console.log(util.inspect(req.params, {showHidden: false, depth: null})) 35 | 36 | res.status(201).send('You are now subscribed!'); 37 | 38 | }); 39 | 40 | app.listen(port, function() { 41 | console.log('Server running on port ', port); 42 | }); 43 | -------------------------------------------------------------------------------- /day2/exercises/forms/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forms", 3 | "version": "1.0.0", 4 | "description": "Showcases how to handle basic form requests", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Marcos", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.16.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /day2/exercises/forms/public/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Search 8 | 9 | 10 | 11 | 12 | 13 |

Search subscriptions

14 | 15 |
16 |

17 | 18 |

19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /day2/exercises/forms/public/subscribe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Newsletter 8 | 9 | 10 | 11 | 12 | 13 |

Susbscribe to our newsletter!

14 | 15 |
16 |

17 | 18 |

19 |

20 | 21 |

22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /day2/exercises/hello/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * app.js 3 | * Main entry point of the hello project 4 | * This script starts a simply hello world server 5 | */ 6 | var express = require('express'); 7 | var app = express(); 8 | 9 | var port = 3000; 10 | 11 | // Handling GET requests 12 | app.get('/', function(req, res){ 13 | res.send('Hello World!'); 14 | }); 15 | 16 | app.listen(port, function() { 17 | console.log('Server running on port ', port); 18 | }); -------------------------------------------------------------------------------- /day2/exercises/mid-static/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * app.js 3 | * Main entry point of the mid-static project 4 | * This script shows how to create a server 5 | * servig static files using app.use middleware feature 6 | */ 7 | var express = require('express'); 8 | var app = express(); 9 | 10 | var port = 3000; 11 | 12 | app.use('/', express.static('public')); 13 | 14 | // Handling GET requests 15 | app.get('/hello', function(req, res){ 16 | res.send('Hello World!'); 17 | }); 18 | 19 | app.listen(port, function() { 20 | console.log('Server running on port ', port); 21 | }); 22 | -------------------------------------------------------------------------------- /day2/exercises/mid-static/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mid-static", 3 | "version": "1.0.0", 4 | "description": "Showcases the static middleware", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Marcos", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.16.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /day2/exercises/mid-static/public/image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbaezpy/trentose2018-nodejs/7923ef1f0df0365a2d2dae5a2ff86b3233a474fc/day2/exercises/mid-static/public/image1.jpg -------------------------------------------------------------------------------- /day2/exercises/mid-static/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Web page example!

4 | 5 |
-------------------------------------------------------------------------------- /day2/exercises/products-api/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * app.js 3 | * Implementation of the Product API 4 | */ 5 | 6 | var express = require('express'); 7 | var bodyParser = require('body-parser'); 8 | 9 | var app = express(); 10 | 11 | app.use(bodyParser.json()); // support json encoded bodies 12 | app.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies 13 | 14 | app.use('/', express.static('public')); 15 | 16 | // starting the server 17 | var port = process.env.PORT || 3000; 18 | app.listen(port, function(){ 19 | console.log('Products server listening at http://localhost:' + port); 20 | }); 21 | 22 | 23 | // list of products we'll keep in memory 24 | var products = [{ 25 | id : 1, 26 | name : "iPhone XL", 27 | description : "Extra large" 28 | }]; 29 | 30 | // Getting an individual product 31 | app.get('/api/products/:id', function (req, res) { 32 | var id = req.params.id; 33 | 34 | if (id > products.length || id < 1) { 35 | res.status(404).send(); 36 | return; 37 | } 38 | 39 | res.send(products[id - 1]); 40 | }); 41 | 42 | // adding a new course to the collection 43 | app.post('/api/products', function (req, res) { 44 | var product = req.body; 45 | product.id = product.length + 1; 46 | 47 | product.push(req.body); 48 | 49 | res.location("/api/products/" + product.id); 50 | res.status(204); 51 | res.send(); 52 | }); 53 | -------------------------------------------------------------------------------- /day2/exercises/products-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "products-api", 3 | "version": "1.0.0", 4 | "description": "Products API server", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Marcos", 10 | "license": "ISC", 11 | "dependencies": { 12 | "body-parser": "^1.18.3", 13 | "express": "^4.16.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /day2/exercises/products-api/public/client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Products demo app 3 | * @author Marcos Baez 4 | */ 5 | $(document).ready(function () { 6 | 7 | var apiUrl = "http://localhost:3000/api"; 8 | 9 | Product.load({ 10 | success : function(collection){ 11 | $.each(collection, function(i, product){ 12 | $(".products").append("
  • {name}
  • ".replace("{name}", product.name) 13 | .replace("{id}", product.id)); 14 | }); 15 | }, 16 | error : function(){ 17 | alert("Products could not be retrieved"); 18 | } 19 | }); 20 | 21 | $("ul").on("click", "li", function(e){ 22 | var id = $(e.currentTarget).attr("id"); 23 | console.log(id); 24 | Product.delete(id, { 25 | success : function(){ 26 | console.log("Deleted!"); 27 | }, 28 | error : function(){ 29 | alert("Error: Could not delete element"); 30 | } 31 | }) 32 | }); 33 | 34 | $(".opt-add").click(function(){ 35 | var data = { 36 | name : $("#pname"), 37 | description : $("#pdesc") 38 | }; 39 | 40 | Product.create(data, { 41 | success : function(){ 42 | window.location.reload(); 43 | } 44 | }); 45 | }); 46 | 47 | 48 | }); 49 | 50 | var BASE_URL = "http://localhost:3000/api"; 51 | 52 | var Product = { 53 | 54 | load : function (cb){ 55 | $.getJSON(BASE_URL + "/products", cb.success).fail(cb.error); 56 | }, 57 | 58 | create : function(data){ 59 | 60 | $.post(BASE_URL + "/products", data, cb.success, "json"); 61 | 62 | }, 63 | 64 | delete : function(id, cb){ 65 | $.ajax({ 66 | url: BASE_URL + "/products/" + id, 67 | type: 'DELETE', 68 | success: cb.success, 69 | error: cb.error 70 | }); 71 | 72 | } 73 | 74 | }; 75 | 76 | 77 | -------------------------------------------------------------------------------- /day2/exercises/products-api/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Products website 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
    18 | 19 | 24 | 25 |
    26 | 27 | 28 | 29 |
    30 | 31 | 32 |
      33 | 34 |
    35 | 36 |
    37 | 38 | 39 | 40 | 41 | --------------------------------------------------------------------------------