### Intro. to Node.js + Express + HTTP ![](https://i.imgur.com/PblG54c.png) Spring 2019 。 Ric Huang --- ### Recap: Backend Server * 還記得這幾次的上課與作業我們都是使用 "create-react-app" 這個工具,自動產生整個 React App 的架構,然後利用 "npm start" 來執行 app. * 但你有沒有好奇過為什麼我們執行 app 是去打開 "localhost:3000", 而不是用 browser 打開 "index.html"? * 你有沒有發現只要你更新任何程式碼,都會自動 trigger "localhost:3000" 的更新? ---- ### Your First Backend Server * Basically, 當你執行 "npm start" 之後,你就啟動了一個 "node.js server", 跑在你的電腦 (i.e. localhost) 上,並且透過 port 3000 來跟外界溝通。 * 而伺服器(server)上面的後端服務(backend service)會一直傾聽相關的需求,例如:發現程式碼有更新,就啟動重新編譯的動作,並刷新服務程式 ---- ### Toward a complete web service * 一個完整的網路服務應用通常要有一個後台(backend),並且有資料庫(database)來儲存用戶或者是服務流程中的相關資料 * 而上述的服務通常會放在一個有固定 IP 的伺服器,並且去申請 domain name, 讓客戶端(client)可以透過 URL(e.g. 網址) 來取得服務 ---- ### Localhost as a local web server * 開發者在開發網路服務的時候,為了測試方便,會先使用自己的電腦來作為後端的伺服器,這就是為什麼會有在開發的時候 backend 與 frontend 都在同一台機器(i.e. localhost)的現象。等到開發到一定程度之後再來 deploy 到雲端 or 機房的機器上面。 ---- 基本上任何可以跑在伺服器(電腦)的程式語言都可以用來實現後端的 server ![](https://i.imgur.com/YneTMvU.png) ---- ### 我們接下來要介紹目前用 JavaScript 做後端最流行的框架:Node.js --- ## Introduction to Node.js * 由 [Ryan Dahl](https://en.wikipedia.org/wiki/Ryan_Dahl) 在 2009 開發,目標在實現 "JavaScript Everywhere" 的典範,讓 web development 可以統一在一個語言底下 * 基本上 Node.js 就是一個 JavaScript 的 runtime environment, **讓 JavaScript 可以在 Browser 以外的環境執行**,所以 Node.js 可以用來開發以 JS 為基礎的後端程式 ---- ### Node.js 的核心就是 Google 開源的 Chrome V8 JS 引擎 (in C++) ![](https://i.imgur.com/fxYlaZn.jpg) ---- **Node.js** 雖然有 .js, 但它並不是指某個單一的 JS 檔案 * 事實上,它是個 JS runtime environment, 允許新增各種用 JS 寫的功能模組 (modules),包含了 file system I/O, networking, binary data (buffers), cryptography functions, data streams, etc. ---- ### Node and npm * 它的各種模組可以透過像是 "npm" (Node Package Management, introduced in 2010) 等工具來管理,而且由於它開源的關係,Node 相關的套件正在以非常驚人的速度在增加 ---- [![](https://i.imgur.com/yAkq92A.png)](http://www.modulecounts.com/) ---- [![](https://i.imgur.com/EhrvNtH.png)](http://www.modulecounts.com/) ---- ### 所以,面對現實,如果你要學 Web Programming, 然後只想 focus 在一個語言,那學 JavsScript 準沒錯,而且 Node.js 是必學! ---- ### 非常活躍的 Node.js Project ![](https://i.imgur.com/MRcjqoO.png) * 下星期 v12 就要出來了! ---- ### Node.js Versions * 每六個月有個 Major Release (四月、十月) * 奇數版本在十月份發行的時候,先前在四月發行的偶數版本就會自動變成 Long Term Support (LTS) 版本, 然後會被積極、持續地維護 18 個月,結束後會被延長維護 12 個月,然後就壽終正寢 * 奇數版本沒有 LTS ---- ### Node.js is singly-threaded, event-driven, and non-blocking I/O ---- ### Recall: Blocking vs. Non-blocking ![](https://i.imgur.com/7HjKYNd.png) ---- * 因為 Node.js 是 singly-threaded, 然後又讓要花較多時間的 I/O 利用 non-blocking 的方式來溝通,因此,可以同時 handle 成千上萬個 events, 而不會有一般平行程式會遇到的 context switching 的 overhead. * 而這些 non-blocking tasks 通常是透過 callback functions 來告知主程式任務完成,並且利用 event loop 來做到非同步 (asynchronous) 的排程,也就是說,事件之間不會有事先固定的順序,而是按照實際完成的先後順序來處理,可以盡量省下事件之間互相等待的情形 --- ### Node.js Modules * 最早 Node.js 是 follow **CommonJS** 的標準來實踐 modules, 但近來已逐漸使用 **ECMAScript Specification** 作為 default ---- ### CommonJS 規範 * CommonJS 的誕生是為了要讓眾多的 JS modules 有一個共同的標準,得以彼此共生在 browser 以外的不同環境底下,建立應用生態系 * 主要包含了 模組規範、套件規範、I/O、File System、Promise 等 * Node.js 就是 CommonJS 的一個主要實踐者 ---- ### CommonJS 是在 runtime 加載(require) modules ```javascript let { stat, exists, readFile } = require('fs'); const math = require('math'); ``` * 然後就可以用了: ```javascript console.log(math.add(1, 2)); ``` * 至於輸出模組,則用 "exports.functionName" ```javascript exports.incrementOne = function (num) { return math.add(num, 1); }; ``` ---- ### ES6 則是強調「靜態時」就要決定模組的相依性 * By **"export"** & **"import"** ```javascript export var firstName = 'Michael'; export function multiply(x, y) { return x * y; } as MM; export class MyClass extends React.Component...; ``` * from 後面的 path 可以是絕對或是相對位址; '.js' 可省 ```javascript import { foo } from './myApp.js'; import { aVeryLongName as someName } from '../someFile.js' ``` ---- ### "export default" * 在前面的例子當中,使用者需要知道 import 進來的檔案裡頭原先的那些變數、function、class 的名字為何,需要跟原來檔案裡頭定義的名字一樣,才可以使用 * **"export default"** 則讓我們可以不用管原來檔案裡頭這些function/class 叫什麼名字,甚至是可以 anonymous ```javascript // Add.js export default (a, b) => (a+b); ``` ---- * 不過既然 function/class 都可以 anonymous 了,所以: 1. export 的檔案就只能有一個 "export default" 的 function or class 2. 在 import 時的名字是屬於 import 那個檔案的 scope 3. from 後面的檔案名稱可以把 .js 省略 ```javascript // import an (anonymous) function from "Add.js" // The name of the function is (re-)named to "MyAdd" import MyAdd from "Add"; ``` ---- ### 講了那麼多,那我要怎麼開使使用 Node.js 以及他的 modules 呢? [Example] Save the following as a file "test.js" ```javascript const http = require('http'); const PORT = process.env.PORT || 3000; const server = http.createServer(function(req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World!'); }); server.listen(PORT, function() { console.log(`Server listening on: http://localhost:${PORT}`); }); ``` * 然後執行 "node test.js" * 打開 "localhost:3000" 看看! ---- * 當然,你也可以直接用 node 的 command line. * 只要在 terminal 鍵入 "node", 你就可以試用各種 Node.js 的指令與模組了! (Use **.help** to get help) ---- ### 我們往後會再教大家如何建立 Node.js server, 現在先來學一個 Node.js 的 "web framework" --- **Express.js** --- ### Learning "Express.js" [[ref](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction)] * (sudo) npm install -g express * (sudo) npm install -g express-generator ---- ### My first Express App * express express-test1 * It will create an "express app" called "express-test1" * cd express-test1 * npm install * npm start * go to "localhost:3000" ---- * 不過,跟 **create-react-app** 不同的是,當你修改檔案的時候,上頁的 node server 並不會自動重啟 * 解決方式:**npm install nodemon --save** * Note: "--save" to save the dependencies to **package.json** * 開啟 **package.json**, 在 "scripts" 裡頭,加上 **"devstart": "nodemon ./bin/www"**, 然後重啟 server by **"npm run devstart"** ---- ### The "express-test1" directory * **./bin/www**: node 開始執行的 script * **./app.js**: 定義 server * **./routes/**: 定義 application 的 routing * **./views**: 定義 application 的 viewing template ---- ### ./bin/www ```javascript var app = require('../app'); var http = require('http'); var port = normalizePort(process.env.PORT || '3000'); app.set('port', port); var server = http.createServer(app); server.listen(port); server.on('error', onError); server.on('listening', onListening); ``` ---- ### ./app.js ```javascript var express = require('express'); var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); var app = express(); app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); app.use(...); // middlewares module.exports = app; // for ./bin/www ``` ---- ### ./routes/index.jx ```javascript var express = require('express'); var router = express.Router(); /* GET home page. */ router.get('/', function(req, res, next) { res.render('index', { title: 'Express' }); }); module.exports = router; ``` * For viewing template, see "./views/index.jade" ```javascript extends layout block content h1= title p Welcome to #{title} ``` ---- ### ./routes/users.js ```javascript var express = require('express'); var router = express.Router(); /* GET users listing. */ router.get('/', function(req, res, next) { res.send('respond with a resource'); }); module.exports = router; ``` * What does the above mean? (also check "app.js") Try [http://localhost:3000/users/](http://localhost:3000/users/) * 試試看改一下檔案,讓 [http://localhost:3000/users/ric](http://localhost:3000/users/ric) 印出 "Ric is cool!" ---- ## Huh? ### 現在是在講什麼? --- ### 試想一下,你有一個用 Node/Express 寫的 Blog service, 放在某個 web server (say, https://www.myblog.com), 你打開 browser, 鍵入上述網址,理應你會載入該網址的 "index.html", 並且同時載入該 HTML 裡頭用到的 JavaScript files. ---- ### 然後你在你看到的前端 Blog 網頁 (well, 就是 "index.html") click on 作者連結、文章連結等,理論上前端就會送一些資料(e.g. 作者/文章 ID)給後端,然後後端收到之後,就會回傳對應的資料(e.g. 作者資訊/文章內容)回來 ---- ### 你有沒有想過... ### 前端與後端是用什麼管道與方式來聯絡的呢? --- ### HTTP * HyperText Transfer Protocol * HTTP Client 跟 Server 之間進行請求與回應的標準 * Version: 1.0 (1996), 2.0 (2015), 3.0 (2018) ---- ### Client Request and Server Response ![](https://i.imgur.com/9tsYg3O.png) ---- ### URL Structure ![](https://i.imgur.com/MkzOorm.png) ---- ### HTTP Request * A Request-line * Zero or more header fields followed by CR/LF * An empty line (i.e., a line with nothing preceding the CR/LF) * Optionally a message-body ---- ### HTTP Request Example ```htmlmixed POST /users/123 HTTP/1.1 // Request-line Host: www.example.com // header fields Accept-Language: en-us // .. Connection: Keep-Alive // .. // empty line licenseID=string&content=string&/paramsXML=string // message-body ``` ---- ### 常見的 HTTP Request Methods [[ref](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods)] * **GET** --- The GET method requests a representation of the specified resource. Requests using GET should only retrieve data. * **POST** --- The POST method is used to submit an entity to the specified resource, often causing a change in state or side effects on the server. * **PUT** --- The PUT method replaces all current representations of the target resource with the request payload. * **DELETE** --- The DELETE method deletes the specified resource. ---- ### 其他 HTTP Request Methods [[ref](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods)] * **HEAD** --- The HEAD method asks for a response identical to that of a GET request, but without the response body. * **PATCH** --- The PATCH method is used to apply partial modifications to a resource. ---- ### An Analogy of HTTP Request Methods [[ref](https://data-sci.info/2015/10/24/%E5%B8%B8%E8%A6%8B%E7%9A%84http-method%E7%9A%84%E4%B8%8D%E5%90%8C%E6%80%A7%E8%B3%AA%E5%88%86%E6%9E%90%EF%BC%9Agetpost%E5%92%8C%E5%85%B6%E4%BB%964%E7%A8%AEmethod%E7%9A%84%E5%B7%AE%E5%88%A5/)] 假設現在我們要點餐, 我們必須先知道菜單是甚麼(get), 我們會向服務生點餐(post), 我們想要取消剛才點的餐點(delete), 我們想要重新點一次(put), 我們想要加點甜點和飲料(patch)。 ---- ### HTTP Response * A Status-line * Zero or more header fields followed by CR/LF * An empty line (i.e., a line with nothing preceding the CR/LF) * Optionally a message-body ---- ### HTTP Response Example ```htmlmixed HTTP/1.1 200 OK // Status-line Accept-Ranges: bytes Cache-Control: public, max-age=0 Connection: keep-alive Content-Length: 53 Content-Type: text/html; charset=UTF-8 Date: Tue, 11 Oct 2016 16:32:33 GMT ETag: "53-1476203499000" Last-Modified: Tue, 11 Oct 2016 16:31:39 GMT // empty line <html> // message-body <body> <h1>Hello, World!</h1> </body> </html> ``` --- ### Express 定義了各種 middlewares ### 來協助 client 與 server 進行溝通 Express is a **routing** and **middleware** web framework that has minimal functionality of its own: An Express application is essentially a series of middleware function calls. ---- Middleware functions are functions that have access to the **request object (req)**, the **response object (res)**, and the **next** middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable named next. ---- ### Middleware Calling Stack ![](https://i.imgur.com/feDovVJ.png) ---- ### Types of Middleware in Express * Application-level middleware * Router-level middleware * Error-handling middleware * Built-in middleware * Third-party middleware ---- ### 基本的 Express Middlewares ```javascript var express = require('express'); var app = express(); app.use([path,] callback [, callback...]); app.METHOD(path, callback [, callback ...]); // METHOD can be: // checkout copy delete get head lock // merge mkactivity mkcol move m-search // notify options patch post purge put // report search subscribe trace unlock // unsubscribe ``` ---- ### 如果 path 沒有指定,則每次都會被執行 ```javascript var app = express() app.use(function (req, res, next) { console.log('Time:', Date.now()) next() }) ``` ---- ### 如果指定 path, 則只有 routing path 符合的時候才會被執行 ```javascript app.use('/user/:id', function (req, res, next) { console.log('Request Type:', req.method) next() }) ``` ---- ### 可以有多個 callback functions ```javascript app.use('/user/:id', function (req, res, next) { console.log('Request URL:', req.originalUrl) next() }, function (req, res, next) { console.log('Request Type:', req.method) next() }) ``` ---- ### 如果沒有 next(), 則執行完就會結束這個 request-response cycle ```javascript app.get('/user/:id', function (req, res, next) { console.log('ID:', req.params.id) next() }, function (req, res, next) { res.send('User Info') }) // This will never be called! app.get('/user/:id', function (req, res, next) { res.end(req.params.id) }) ``` ---- ### 也可以傳 array of callbacks ```javascript function logOriginalUrl (req, res, next) { console.log('Request URL:', req.originalUrl) next() } function logMethod(req, res, next) { console.log('Request Type:', req.method) next() } var logStuff = [logOriginalUrl, logMethod] app.get('/user/:id', logStuff, function (req, res, next) { res.send('User Info') }) ``` --- ### 今天先教到這邊 * 這部分要學好,只有上課聽 + practice 是不夠的 * 下次上課前請先自行完成以下的 pre-readings: * Express 官網關於 Middleware 以及 APIs 的描述:https://expressjs.com/ * MDN Express tutorials:[[link](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website)] * 另外,下次上課前也請了解一下 Promise/Async/Await * MDN [[Using Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises)] [[Promsie](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)] * MDN [[async function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)] ---- ### 是的,這週沒有 practice or homework ### 請務必自行研讀上述的 materials, 或者是找尋其他相關的 online tutorials --- ## See you next week!
{"metaMigratedAt":"2023-06-14T21:08:38.435Z","metaMigratedFrom":"YAML","title":"Introduction to Node.js + Express + HTTP (04/17)","breaks":true,"slideOptions":"{\"theme\":\"beige\",\"transition\":\"fade\",\"slidenumber\":true}","contributors":"[{\"id\":\"752a44cb-2596-4186-8de2-038ab32eec6b\",\"add\":15633,\"del\":1673},{\"id\":\"a202b02b-072b-4474-be14-c5a3f8932dbb\",\"add\":38,\"del\":0}]"}
    2100 views