# NodeJS(Max)第 3 節:Understanding the basics > Udemy課程:[NodeJS - The Complete Guide (MVC, REST APIs, GraphQL, Deno) ](https://www.udemy.com/course/nodejs-the-complete-guide/) `20231122Wed.~20231203Sun.` ## 3-25. How The Web Works ![image](https://hackmd.io/_uploads/BJZx5QoN6.png) **** ## 3-26. Creating a Node Server * node.js的檔案進入點為"app.js" * node.js的core module有以下幾種 * http:Launch a server, send requests * https:Launch a SSL server * fs * path * os :::success 一個node.js文件,就是一個單獨的moduel。 ::: * node server實作: * ==require()==:require method提供可以載入module方法(例如上面提到的core module們都可以利用require()載入。) ++require加載module 流程++: 1. 找到需要加載的module 2. 判斷是否緩存過,若沒有則讀取module -->避免多次引入相同module 3. 把讀取到的內容放到一個自執行函數中執行 ++require()加載module基本上是「同步」的。++ ![image](https://hackmd.io/_uploads/BkVZpQsEa.png) 首先,載入http module,載入之後我們就可以使用"http object",http object提供很多fields、methods給我們使用。 ```javascript! const http = require("http"); ``` * ==http.createServer()==: 1. run your computer into sever 2. create a Http sever object * 寫法: 1. 基本函式寫法 ```javascript! finction reListener(req, res){ ... } http.createServer(reListener()); ``` 2. 匿名函式寫法 ```javascript! http.createServer(function(req, res){ ... }); ``` 3. 箭頭函式寫法 ```javascript! http.createServer((req, res) => { ... }) ``` * 用以上任意方法建立好server之後,賦予他一個變數(此處叫做server): ```javascript! const http = require("http"); const server = http.createServer((req, res) => { console.log(req); }) ``` 如此一來,現在這個server變數便是一個Http sever object,理所當然的也會有一些methods供我們使用: ![2023-11-22 15-43-14 的螢幕擷圖](https://hackmd.io/_uploads/ryed7bEi4T.png) > 圖片參考:[w3schools-Node.js HTTP Server Object](https://www.w3schools.com/nodejs/obj_http_server.asp) * ==listen()==:承上面提到的Http sever object,listen()是該物件的其中一個method。 1. listen()可以接收參數port,這裡給予3001(若沒給則會跑預設的port,通常是8080),我們跑這段程式"node app.js",可以從terminal發現他在運作。 ![2023-11-22 15-47-20 的螢幕擷圖](https://hackmd.io/_uploads/SJZDmEjEp.png) 2. 之後我們到browser輸入"localhost:3001",雖然browser看不到任何動作(畢竟我們還沒return任何html頁面) ![2023-11-22 15-50-06 的螢幕擷圖](https://hackmd.io/_uploads/ByV0mEjE6.png) 3. 不過回到terminal看到一大串東西,那些東西就是我們程式碼console.log(req)的結果,所以那一大陀東西就是我們(使用者)輸入網址後,所傳送到server端的request。 ![2023-11-22 15-55-20 的螢幕擷圖](https://hackmd.io/_uploads/HJFg4EsNT.png) **** ## 3-27. The Node Lifecycle & Event Loop ![image](https://hackmd.io/_uploads/r12Mv4iV6.png) **** ## 3-29. Understanding Requests ![image](https://hackmd.io/_uploads/HyVQwSXSp.png) ```javascript! const http = require("http"); const server = http.createServer((req, res) => { console.log(req.url, req.method, req.headers); }) server.listen(3000); ``` req為request物件 1. <font style="color: red;">req.url</font>: 目前位址是localhost:3000的根目錄,所以只會得到一個斜槓 2. <font style="color: orange;">req.method</font>: 目前的方法為GET 3. <font style="color: yellow;">req.headers</font>: 下面一大陀都是headers **** ## 3-30. Sending Responses ![image0](https://hackmd.io/_uploads/Bkl6urEHT.jpg) ```javascript! const http = require("http"); const server = http.createServer((req, res) => { console.log(req.url, req.method, req.headers); res.setHeader("content-type", "text/html"); res.write("<html>"); res.write("<head><title>My First Page</title></head>"); res.write("<body><h1>Hello from my Node.js Server!</h1></body>"); res.write("</html>"); res.end(); }) server.listen(3000); ``` > 參考資料: > 1. [res.setHeader 方法和res.writeHead 方法|學習筆記](https://developer.aliyun.com/article/1060024) > 2. [Express框架中res.write、res.end及res.send 、res.json方法之間的區別?](https://blog.csdn.net/sunyctf/article/details/124616674) res為response物件。 1. <font style="color: red;">res.setHeader</font>: Header告訴瀏覽器我發送的資料是什麼類型的,你應該用什麼格式來編碼顯示。如果不設定,會自動產生一個回應頭,但中文的話瀏覽器會亂碼。 在http 協定中, Content-type 是用來告訴對方我寄給你的資料內容是什麼類型。 2. <font style="color: orange;">res.write</font>: ![2023-11-29 13-18-48 的螢幕擷圖](https://hackmd.io/_uploads/HkcCFB4S6.png) 3. <font style="color: yellow;">res.end</font>: 每個請求都必須要呼叫的一個方法res.end();。結束回應(請求)告訴伺服器該回應的封包頭、封包檔案等等全部已經回應完畢了,可以考慮本回應結束。res.end()要回應資料的話,資料必須是String類型或是Buffer類型。 **** ## 3-32. Routing Requests ![image](https://hackmd.io/_uploads/S1OQhIVHa.png) ```javascript! const http = require("http"); const server = http.createServer((req, res) => { const url = req.url; if(url === "/"){ res.setHeader("content-type", "text/html"); res.write("<html>"); res.write("<head><title>Enter Message</title></head>"); res.write("<body><form action='/message' method='POST'><input type='text'><button type='submit'>send</button></form></body>"); res.write("</html>"); return res.end(); } res.setHeader("content-type", "text/html"); res.write("<html>"); res.write("<head><title>My First Page</title></head>"); res.write("<body><h1>Hello from my Node.js Server!</h1></body>"); res.write("</html>"); res.end(); }) server.listen(3000); ``` 1. action:當提交表單時,action屬性代表著「從form收集來的data」會被送往某處。像上方例子便是送往`localhost/message`。 2. method:HTTP傳送data的方法可以是GET或是POST **GET request** is automatically send when you click a link or enter a url. **POST request** has to be set up by you, by creatingsuch a form etc. **** ## 3-33. Redirecting Requests ![2023-11-29 15-02-41 的螢幕擷圖](https://hackmd.io/_uploads/rkD7zwNHT.png) ▲ 原先:尚未提交表單 ![image](https://hackmd.io/_uploads/rJLd4PEBT.png) ![image](https://hackmd.io/_uploads/B18tNv4Sa.png) ![image](https://hackmd.io/_uploads/rkg5NvVrT.png) ▲ 重新導向request:提交表單之後 ```javascript! const http = require("http"); const fs = require("fs"); const server = http.createServer((req, res) => { const url = req.url; const method = req.method; if(url === "/"){ res.setHeader("content-type", "text/html"); res.write("<html>"); res.write("<head><title>Enter Message</title></head>"); res.write("<body><form action='/message' method='POST'><input type='text'><button type='submit'>send</button></form></body>"); res.write("</html>"); return res.end(); } if(url === "/message" && method === "POST"){ fs.writeFileSync("message.txt", "DUMMY"); res.statusCode = 302; res.setHeader("Location", "/"); return res.end(); } res.setHeader("content-type", "text/html"); res.write("<html>"); res.write("<head><title>My First Page</title></head>"); res.write("<body><h1>Hello from my Node.js Server!</h1></body>"); res.write("</html>"); res.end(); }) server.listen(3000); ``` **** ## 3-34. Parsing Request Bodies :::success 擷取至[【stack overflow】In node.js "request.on" what is it this ".on"](https://stackoverflow.com/questions/12892717/in-node-js-request-on-what-is-it-this-on) <p>The <strong>on</strong> method <strong>binds</strong> an event to a object. </p> <p>It is a way to express your intent <strong>if</strong> there is something happening (data sent or error in your case) , <strong>then</strong> execute the function added as a parameter. This style of programming is called <strong>Event-driven programming</strong>. You might want to look it up in the <a href="http://en.wikipedia.org/wiki/Event-driven_programming" rel="noreferrer">Wikipedia</a></p> <p>In node.js, there is a class called <strong>EventEmitter</strong> that provides you with all the code that you need for basic events if you decide to use them in your own code (which I would strongly recommend in the case of node.js). Docs for node.js <strong>EventEmitter</strong> are <a href="http://nodejs.org/api/events.html" rel="noreferrer">here</a></p> ::: > 參考資料: > 1. [Node.js 中的缓冲区(Buffer)究竟是什么?](https://mp.weixin.qq.com/s/UU-Gug_Dx-OmXVL-99rWRg) > 2. [In node.js "request.on" what is it this ".on"](https://stackoverflow.com/questions/12892717/in-node-js-request-on-what-is-it-this-on) > 3. [Node.js Streams: Everything you need to know](https://www.freecodecamp.org/news/node-js-streams-everything-you-need-to-know-c9141306be93) **** request object是一種EventEmitter object,代表可以發出(emit)事件(event),那request object可以發出哪些事件呢? 這裡先把request object全部印出來,可以從中找到`_events`,底下的所有event即request object會發出的事件,我們可以新增listener來監聽這些事件。 ![image](https://hackmd.io/_uploads/rJwwt_rra.png) 所以當我們使用req.on(eventName, listener)時,.on在這裡可以用來監聽request object所發出的事件(把它放到eventName的位置),而listener則是在eventName被觸發時將會執行的函式。 ![image](https://hackmd.io/_uploads/S1nL9uBrp.png) 先來看看什麼情況會發出事件,首先是**當eventName為"data"時**。 這裡用以下作code做為舉例,我們目前有一個form,並且輸入"abcd": ![2023-11-30 10-59-46 的螢幕擷圖](https://hackmd.io/_uploads/rk4GsdSBT.png) ```javascript! const server = http.createServer((req, res) => { ... res.write("<html>"); res.write("<head><title>Enter Message</title></head>"); res.write("<body><form action='/message' method='POST'><input type='text' name='message'><button type='submit'>send</button></form></body>"); ... req.on("data", (chunk) => { console.log(chunk); }) ... } ``` 接著當我們按下send按鈕之後,這個"abcd"就會被作為data POST到我們上方設置的`/message`中,當我們傳送了data,data送達了目的,便會觸發listener。 另外一個例子是**當eventName為"end"時**。end event的觸發條件是在所有的data皆傳送完畢,不再有data傳送時便會發出end event。 另外,要記得`<input>`中必須要有"name"屬性,這樣當使用者輸入的內容作為data放進request body時,以上例來說就是message才可以作為一組鍵值對(key-value-pair)的keys,而使用者輸入的"abcd"才可以作為該組鍵值對(key-value-pair)的value。 **** > 參考資料: > 1. [Node.js官方文件:emitter.on(eventName, listener)](https://nodejs.org/api/events.html#emitteroneventname-listener) > 2. [When does the NodeJS request object emit events?](https://stackoverflow.com/questions/57350966/when-does-the-nodejs-request-object-emit-events) ```! [註] const 用const宣告變數之後,就不能再次宣告或是重複指定值。 例如上方例子中的const body = [];,宣告之後我們不能再次body=某某東西,因為這樣的行為就是在對已宣告的body重複assign值了。 不過我們可以對body做push,body.push(某某東西)是沒問題的,因為我們這時改變的是body object(或body element)後面的data,而非value本身。 ``` **** ## 3-35. Understanding Event Driven Code Execution 先前提到的req.on(eventName, listener),其中的listener屬於callback function,只有在我們呼叫時他才會作用。 **** ## 3-36. Blocking and Non-Blocking Code 在3-34中的程式碼,有一行叫作`fs.writeFileSync("message.txt", message);`,他有個關鍵字sync,是synchronize的縮寫,即同步的意思,這在3-25的圖中解釋過,也就是說經過這行程式時,他會把程式的運行給block住,得等這行`fs.writeFileSync("message.txt", message);`執行完畢,程式才會繼續往下執行。 ![image](https://hackmd.io/_uploads/BkVZpQsEa.png) 但假如這行程式遇到了問題,我們的程式就會卡在那邊不動了,因為他是同步的,我們得等這行程式結束後才能執行,所以我們不應該用這樣的語法來寫。 取而代之的,我們應該利用這行`fs.writeFile("message.txt", message);`作為取代。 他可以在第三個參數放上一個function,這個function仍然是個callback function,而該function可以接受error object,所以當沒有error時將會得到null,也就是說當有error時即為true,該function便會被執行。 ```javascript! fs.writeFile("message.txt", message, err => { res.statusCode = 302; res.setHeader("Location", "/"); return res.end(); }); ``` ## 3-38. Using the Node Modules System **▊ 原先** 接著要把先前的程式碼給模組化,所以先來看看原先的程式碼,我們把所有的邏輯都放在同一個檔案`app.js`中。 **app.js** ```javascript! const http = require("http"); const fs = require("fs"); const server = http.createServer((req, res) => { const url = req.url; const method = req.method; if(url === "/"){ res.setHeader("content-type", "text/html"); res.write("<html>"); res.write("<head><title>Enter Message</title></head>"); res.write("<body><form action='/message' method='POST'><input type='text' name='message'><button type='submit'>send</button></form></body>"); res.write("</html>"); return res.end(); } if(url === "/message" && method === "POST"){ const body = []; req.on("data", (chunk) => { console.log(chunk); body.push(chunk); }) return req.on("end", () => { const parsedBody = Buffer.concat(body).toString(); const message = parsedBody.split("=")[1]; fs.writeFile("message.txt", message,(err) => { res.statusCode = 302; res.setHeader("Location", "/"); return res.end(); }); }) } res.setHeader("content-type", "text/html"); res.write("<html>"); res.write("<head><title>My First Page</title></head>"); res.write("<body><h1>Hello from my Node.js Server!</h1></body>"); res.write("</html>"); res.end(); }) server.listen(3000); ``` **▊ 模組化** 所以現在我們要把跟route相關的邏輯全部拉出來獨立成一個檔案。 這裡會運用到module.expoorts的語法。例如下方例子,我們若是在其他檔案中import 某個模組,那麼nodejs便會去尋找module.exports,並從中尋找是否有東西在某個檔案中register了。 ```javascript! const requestHandler = (req, res) => { ... } module.exports = requestHandler; //註冊(register)requestHandler函式 ``` ![image](https://hackmd.io/_uploads/ByE1dvuH6.png) **routes.js** ```javascript! const fs = require("fs"); const requestHandler = (req, res) => { const url = req.url; const method = req.method; if(url === "/"){ res.setHeader("content-type", "text/html"); res.write("<html>"); res.write("<head><title>Enter Message</title></head>"); res.write("<body><form action='/message' method='POST'><input type='text' name='message'><button type='submit'>send</button></form></body>"); res.write("</html>"); return res.end(); } if(url === "/message" && method === "POST"){ const body = []; req.on("data", (chunk) => { console.log(chunk); body.push(chunk); }) return req.on("end", () => { const parsedBody = Buffer.concat(body).toString(); const message = parsedBody.split("=")[1]; fs.writeFile("message.txt", message,(err) => { res.statusCode = 302; res.setHeader("Location", "/"); return res.end(); }); }) } res.setHeader("content-type", "text/html"); res.write("<html>"); res.write("<head><title>My First Page</title></head>"); res.write("<body><h1>Hello from my Node.js Server!</h1></body>"); res.write("</html>"); res.end(); } module.exports = requestHandler; ``` **app.js** ```javascript! const http = require("http"); // routes is a customer file const routes = require("./routes") const server = http.createServer(routes); server.listen(3000); ``` 而nodejs提供了一種shortcut寫法,不必寫出`module.exports = requestHandler;`,只須寫出`exports = requestHandler;`即可。 **** ## Assignment ![2023-12-02 16-35-02 的螢幕擷圖](https://hackmd.io/_uploads/HkaQpw_Sa.png) **Answer** ```javascript! const http = require("http"); const server = http.createServer((req, res) => { const url = req.url; if(url === "/"){ res.setHeader("content-type", "text/html"); res.write("<html>"); res.write("<head><title>Welcome!</title></head>"); res.write(` <body> <form action='/create-user' method='POST'> <input type='text' name='username'> <button type='submit'>send</button> </form> </body> `); res.write("</html>"); return res.end(); } if(url === "/users"){ res.setHeader("content-type", "text/html"); res.write("<html>"); res.write("<head><title>Enter Message</title></head>"); res.write("<body><ul><li>user 1</li><li>user 2</li></ul></body>"); res.write("</html>"); return res.end(); } if(url === "/create-user"){ const body = []; req.on("data",(chunk) => { body.push(chunk); }); req.on("end", () => { const parsedBody = Buffer.concat(body).toString(); //username=使用者輸入的任何內容 console.log(parsedBody.split("=")[1]); }); } res.statusCode = 302; res.setHeader("Location", "/"); res.end(); }); server.listen("3000"); ``` **** ## 其他參考資料 1. Official Node.js Docs: https://nodejs.org/en/docs/guides/ 2. Full Node.js Reference (for all core modules): https://nodejs.org/dist/latest/docs/api/ 3. More about the Node.js Event Loop: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/ 4. Blocking and Non-Blocking Code: https://nodejs.org/en/docs/guides/dont-block-the-event-loop/