[學習之路] Node.js 入門教學
Node.js 是能夠在伺服器上面運行 JavaScript 的應用平台環境,透過 Node.js 提供的函式庫與執行環境能完成伺服器端服務,提供各種網路應用。本篇資源版本與環境如下:
- 主題教材:
- Node.js 官方網站 版本 v14.15.3
- NVM 版本 v0.37.2
- NVM-windows 版本 v1.1.7
- IDE 編譯器:VScode v1.52.1
- 作業系統:Windows 10 NT
認識
- Node.js 透過安裝到指定伺服器主機內,使用 JavaScrpt 進行語法撰寫。常用於即時與推播等網路應用,像是 WebServer、聊天機器人、資料存取服務等網路應用。
- 早期是採用 C 與 Lua 程式語言開發但並不順利,直到 Google V8 JavaScript 引擎出現才成功,透過該引擎執行 JavaScript 代碼。
- Node.js 本身是單執行緒,因此採用 Non-blocking I/O 與非同步事件來規劃程式運作,這能讓大量使用者連線情況下不會因為等待 IO 等待回應而占用執行緒。
- Node.js 提供多個模組的 API 函式庫,包含一些非同步的 IO 檔案存取、Socket、HTTP 等函式庫,所以 Node.js 自己就能創造 Web 伺服器,不需依賴 IIS 或 Apache 伺服器。
- 用戶端的 JS 全域物件為 window 物件,Node.js 的 JS 全域物件為 Global 物件。
- Node.js 能在全域下使用 console 物件 與 setTimeout/setInterval 函式,用法都跟一般的 JS 相同。
安裝
Node.js 作為伺服器應用平台,你需要提供一台主機作為伺服器運作。本教學使用 Windows 為伺服器環境,並提供三種安裝 Node.js 方式教學:
官網下載
前往官網提供穩定版 (LTS) 與最新版 (Current) 可選擇,選擇穩定版即可。安裝方式大多簡單快速通過即可,有幾個過程項目需注意:
- 目前版本預設情況下會自動幫你安裝 npm 管理與設定 PATH 所需路徑。所以不用擔心需要額外安裝或設定什麼
- 會提供一個 Automaticy install 的選項自動安裝工具請打勾,這能幫助你未來遇到一些 npm 模組可提供修改時呼叫 Python 與 VScode 來連動修改。若未安裝時未來會出現錯誤訊息 (ex:gyp ERR! find Python)。若有打勾將會主程式安裝完畢後呼叫 cmd 進行此項目工具安裝。
安裝完畢後執行 cmd 或 powershell,輸入 node -v
或 npm -v
能得到版本資訊代表安裝成功。
NVM 工具
全名為 Node Version Manager,為 Caswell 作者開發出來的強大管理工具,主要是由於工作上處理專案時需要不同的 Node 版本來執行(因有些 npm 模組有版本相容性問題)。大多數的開發者都會選擇 NVM 來作為 node 安裝來源。
根據手冊說明安裝十分簡單,開啟 cmd 輸入以下指令(擇一)即可,但實際上僅適用於 Mac / Linux 應用
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash |
我們環境是 windows 無法使用,所以需要找支援 windows 的另一家 NVM 工具,這裡提供 nvs 來使用。(另外常用的還有 nvm-windows 選擇) 讓我們下載 nvm-setup.zip 使用。
安裝過程也十分簡單沒什麼選項,之後開啟 cmd 或 powershell 輸入 nvs
能列出版本選擇初次安裝。之後再次輸入 nvs
能選擇使用 node 版本或另外安裝。指令簡單明瞭。
C:\Users\Loki> nvs |
如此一來你就獲得 node 與 npm 的安裝,如果工作需要可多安裝幾個版本。需要切換 node 版本時再透過指令 nvs
做畫面選擇即可。注意每個 node 版本都是獨立的 npm 套件不共用。
USB 免安裝
因為工作需要不同電腦上執行,因此我習慣用 USB 作免安裝環境。因此 VScode、GIT、Nodejs 都能使用免安裝。同時依賴 VSCode 的同步功能自動把 GIT 跟 Nodejs 相關檔案都一起同步化。這裡將會順便說明 GIT 如何加入到免安裝:
VSCode 的免安裝
使用安裝不特別說明,本站舊文或網路上曾提到可自行參考。另外根據官方 Portable 的資料保留方式,之後也會把 Git 與 Node 的檔案都放入到 (VSCode folder)\data\user-data
內,這樣方便 VSCode 進行 Setting Sync 時一同備份保留。
GIT 的免安裝
到 GIT 網站 下載頁面,點選對應的 windows 作業系統在觸發下載(自動下載請直接取消)的頁面上找到 64-bit Git for Windows Portable。下載解壓到 USB 位置的 VSCode 路徑 (VSCode folder)\data\user-data\User\Lokitools\Git
。
Node 的免安裝 (兩種方式)
- 方法一:官方的 Node
到 Nodejs 網站 下載頁面,點選對應的 windows 找到 Windows Binary (.zip) 連結,下載解壓到 USB 位置的 VSCode 路徑(VSCode folder)\data\user-data\User\lokiTools\node
- 方法二:NVS 管理器 (本篇使用方式為此)
到 NVS 的 release 處 下載頁面,點選 Source Code 版本連結,下載解壓到 USB 位置的 VSCode 路徑(VSCode folder)\data\user-data\User\lokiTools\nvs
移植舊 npm 模組
如果已經有 Node 有相關 npm 模組,記得搬移進去到你的 node\node_modules
內,node 根目錄的一些模組主程式也要(舉例 hexo,hexo.cmd,hexo.ps1 也對應到新位置根目錄)。
讓 VScode 終端機自動執行 GIT 與 Node
大致有三個重點要處理:
- 讓 VSCode 接管你的 GIT 執行程式。
- 讓 VSCode 終端機是使用 windows cmd 為執行環境。
- 讓 node,npm,git 的 windows PATH 路徑於每次操作終端機之前寫入到該電腦的 PATH 環境內,使不同電腦上都能正常運作。
開始設計步驟如下:
目前所有已持有的檔案都在
(VSCode folder)\data\user-data\User\lokiTools\
內,裡面有 Git,node(或 nvs) 資料夾。在該資料夾底下建立
autoRunOnVSCode.bat
批次檔案,讓 VSCode 在使用終端機時自動跑這個前置作業。批次檔案主要處理宣告 PATH(Git 與 node 路徑)寫入到目前電腦的 PATH 環境內,以及顯示相關版本資訊。
這裡提供兩種版本,分別對應前面方法一(官方 node) 與方法二 (nvs) 的寫法,擇一即可。
autoRunOnVSCode.bat by Nodejs @echo off
:::::::::::::::::::::::::::::::::::::::::: Put into Windows PATH
:: GIT
set gitdir=%~dp0\Git\cmd
:: Method 1 : node PATH
set nodedir=%~dp0\node
set path=%PATH%;%gitdir%;%nodedir%
:::::::::::::::::::::::::::::::::::::::::: Print Message
:: Figure out versions for Git, Node.Js, and NPM. This first one breaks apart the Git version to make it look nicer.
for /f "tokens=3-6 delims=. " %%a in ('git --version') do (set gitver1=%%a&set gitver2=%%b&set gitver3=%%c)
echo Git Version = v%gitver1%.%gitver2%.%gitver3%
for /f "tokens=1" %%v in ('node -v') do set nodever=%%v
echo Node Version = %nodever%
for /f "tokens=1" %%n in ('npm -v') do set npmver=%%n
echo NPM Version = v%npmver%autoRunOnVSCode.bat by nvs @echo off
:::::::::::::::::::::::::::::::::::::::::: Put into Windows PATH
:: GIT
set gitdir=%~dp0\Git\cmd
:: method 2 : nvs (node version switcher) PATH
set nvsdir=%~dp0\nvs
set path=%PATH%;%gitdir%;%nvsdir%
call %~dp0\nvs\nvs.cmd
:::::::::::::::::::::::::::::::::::::::::: Print Message
echo -------------------------
for /f "tokens=1" %%n in ('nvs -v') do set nvsver=%%n
echo NVS Version = v%nvsver%
echo -------------------------
:: Figure out versions for Git, Node.Js, and NPM. This first one breaks apart the Git version to make it look nicer.
for /f "tokens=3-6 delims=. " %%a in ('git --version') do (set gitver1=%%a&set gitver2=%%b&set gitver3=%%c)
echo Git Version = v%gitver1%.%gitver2%.%gitver3%
for /f "tokens=1" %%v in ('node -v') do set nodever=%%v
echo Node Version = %nodever%
for /f "tokens=1" %%n in ('npm -v') do set npmver=%%n
echo NPM Version = v%npmver%最後是設定 VSCode 的
settings.json
,路徑設定走絕對路徑(舉例這裡 USB 固定為 K 槽),其三個重點:- 設定 git 的執行擋在我們免安裝隨身碟之處
- 設定終端機採用 windows 內建的 cmd 命令字元
- 讓每次使用 VSCode 的終端機時會自動執行這個
autoRunOnVSCode.bat
批次檔settings.json //for cmd and git path
"editor.renameOnType": true,
"git.autofetch": true,
"git.path": "K:\\VSCODE\\data\\user-data\\User\\lokiTools\\Git\\bin\\git.exe",
"terminal.integrated.shell.linux": "",
"terminal.integrated.shell.windows": "C:\\WINDOWS\\System32\\cmd.exe",
"terminal.integrated.shellArgs.windows": [
"/K",
"K:\\VSCODE\\data\\user-data\\User\\lokiTools\\autoRunOnVSCode.bat"
],
目前為止已可成功使用免安裝隨身碟,只要保持 USB 磁碟機為固定代號就能使用。
入門操作
Node 操作原理是透過終端機指令 node *.js
去解讀 JS 語法。如果有 console 時會直接由 node 回應給終端機輸出。假設有個檔案為:
console.log("Hello World"); |
透過 node 去執行這個檔案你會在終端機得到一個訊息。
L:\nodeTest>node helloworld.js |
或者採用進入 node REPL 命令模式,在關鍵字 node
送出後,就能直接在終端機上輸入 node 指令:
L:\nodeTest>node |
在 REPL 模式內妳可以使用以下快速鍵:
- ctrl + c:退出當前終端。
- ctrl + c:單擊兩次-退出 Node REPL。
- ctrl + d:退出節點 REPL。
- 上下鍵:查看輸入的歷史命令
- tab 鍵:列出當前命令
- .help:列出使用命令
- .break:退出多行表達式
- .clear:退出多行表達式
- .save filename:保存當前的 Node REPL 模式到指定檔案
- .load filename:加載當前節點 REPL 模式的檔案內容。
process 物件
內建的 process 物件 能代表目前正執行代碼之流程運作,你可以控制當下 process 之事件、屬性、方法。可使用的資源多可參考官方手冊做了解,這裡舉例兩個操作:
exit 中斷操作
當代碼遇到 process.exit()
代表當前執行碼結束。或使用 event 事件遇到 exit 行程結束時觸發指定動作。
console.log(1); |
nextTick 指定下次執行
由於 Node.js 採用事件迴圈方式來消化代碼,使得非同步與非阻擋式 I/O 能順利後續處理。你也能指定將 callback 內容安排至下一次事件迴圈時第一個執行處。
console.log(1); |
全域變數
執行 Node 應用時,能透過 __filename
與 __dirname
全域變數來取得該應用程式所在之檔案名稱或目錄,對於需要找到相對路徑處理上有很大的幫助。
console.log(` |
L:\nodeDemo>node . |
Moudles 模組
在設計 Node 應用時,由於是函式庫觀念,所有的可用函式都是使用模組系統觀念來運用,透過 require 以及 exports 方式提供檔案和模組之間的溝通。大致上可以分為三種類型:
- Third Party Modules (第三方模組)
- Local Modules (自建模組)
- Core Modules (原生模組)
Third Party Modules (第三方模組)
第三方模組代表是別人寫好的自建模組(稍晚提到),直接將檔案放入本地端目錄下以相對路徑 requare 使用即可。或者也可透過網路使用 npm 取得別人上傳的模組。全名為 Node Package Manager 的 npm 是 Node.js 額外套件管理系統,安裝 node 主程式時就已安裝好。透過 npm 能夠輕易安裝各家套件模組。想知道有那些第三方模組可使用可以到網站 npm | build amazing things 找到,或者透過終端機指令 npm search <MOUDLE_NAME>
進行搜尋。同樣的你也可以將自建模組放到 npm 上面去提供別人下載使用。
:::::::::::::::: 搜尋可用的安裝模組 |
全域安裝 vs 區域安裝
使用 npm 安裝第三方模組可分為全域或區域兩種安裝位置且會有不同的應用考量。全域會影響整個 node.js 的所有專案應用。區域即只會影響該專案目錄應用。
Global Install 全域安裝
- 語法為
npm install <Module Name> -g
,安裝位置會在/user/local(或 Node 安裝目錄)的 node_modules 目錄下 - 安裝位置在哪可透過指令
npm root -g
查詢(可由npm config set ''
來修改位置):::::::::: 全域安裝路徑 ::::::
L:\nodeTest>npm root -g
K:\VSCODE\data\user-data\User\lokiTools\nvs\node\14.15.3\x64\node_modules
:::::::::Node 參數清單 ::::::
L:\nodeTest>npm config ls
; cli configs
metrics-registry = "https://registry.npmjs.org/"
scope = ""
user-agent = "npm/6.14.9 node/v14.15.3 win32 x64"
; node bin location = K:\VSCODE\data\user-data\User\lokiTools\nvs\node\14.15.3\x64\node.exe
; cwd = L:\nodeTest
; HOME = C:\Users\Loki-Home
; "npm config ls -l" to show all defaults.
::::::::: 安裝目錄路徑:::::::
L:\nodeTest>npm config get prefix
K:\VSCODE\data\user-data\User\lokiTools\nvs\node\14.15.3\x64 - 一旦選擇全域方式安裝,該模組將被全域所能使用。也就是可以直接在命令列上使用模組。
- 一個模組就能被所有專案的 node 所使用避免過多的版本差異,但對個別專案的版本依賴有困難。
舉例來說 npm 就是一個全域型的原件模組,可透過 list(或 ls) 指令確認(透過-g 為指定 global 全域):
L:\nodeDemo>npm list -g npm |
Local Install 區域安裝
- 語法為
npm install <Module/Package Name>
,安裝到在目前終端機位置的node_modules
目錄下 - 如果需要使用該模組時必須使用
require(<Moudle Name>)
才能載入使用 - 只有該專案目錄內才能找到該模組,其他目錄下無法找到。因此可避免不同專案內的版本衝突 (A 案需 ver1,B 案需 ver3)
舉例來說:我們先在目錄 nodeTest 下安裝 express,nodeTest 會多一個 node_moudles 資料夾且可查詢,接著到另一個專案目錄 nodeDemo 做查詢結果。
::: 本地安裝模組 express |
npm 其他指令
你可以使用 npm 進行一些常用操作:
- npm search <NAME>
已知的模組名稱進行搜尋檢查可用安裝與版本資訊 - npm install|update|uninstall <NAME>
對已安裝過後的模組進行版本安裝、升級、移除。 - npm help
查找 npm 相關指令 - npm cache clear
清除 npm 本地暫存 - npm publish|unpublish <package>@<version>
將目前專案目錄發佈到 npm 套件管理系統給 Nodejs 社群使用,也能取消指定的某版本
:::::::::::::::: 更新已安裝的模組 |
Local Modules(自建模組)
自建模組就是自己寫一個模組提供給自己應用。模組單位為一個 js 檔案,跟 node 應用一樣都是 js 檔,但應用是透過指令 node app.js
來執行,模組是在應用內宣告 request ('mod.js')
來載入,應用與模組兩種用法不同。自建模組也能打包成 npm 且分享至 npm 平台去。
手動的自建模組
進行自建模組編寫時,最後需使用 export 方式傳出去。屆時任何應用進行 request 載入時才能正常接洽使用回傳內容。export 本身是一個變數,它屬於整個 module 模型下的原生練物件。因此只要將想回傳的東西指定給 export(可以是字串或文字)即可。
產生 resport 回傳
跟隨範例,設計一個模組在專案目錄下建立 mod.js 進行設計。注意需要將資料回傳給應用時需指定 exports 來回應。
方法一:將 exports 當作物件,指定三種名稱函式
exports.en = function (userName) { |
方法二:將完整的物件指定給 export
const hello = { |
發出 request 取得結果
接著建立 test.js 檔案編寫 Node 應用,注意檔案路徑上的差異。我們透過 request 來請求載入結果存成變數,這個 MyMod 將是一個物件資料。就能直接指定物件屬性來取得函式結果。
/* |
執行應用程式
透過終端機執行 node 應用執行 test.js 程式,得到以下結果。
L:\nodeDemo>node test.js |
npm 的自建模組
可使用 npm 元件方式新建立模組。在 npm 話題內所有的模組都是 package 安裝包觀念,因此每一個模組內都會有 package.json 檔案提供這個安裝包的詳細資訊。
- 透過
npm init
方式初始化 package(將要求你輸入一些基本資料)。 - 一個 package 可以跟別人的 package 形成依賴關係使用。如果你安裝了 Package A 時,可能這個 A 會自動幫你下載其他指定的 Package BCDE… 利於本體 A 的正常運行。
- 所有的 npm 模組都有自己的 package.json 提供相關資訊參數,所以自己的 node 自訂模組也會有 package.json。
- modules 是指一個功能程式之模組(不需 package.json);package 是指一個具備 package.json 的完整安裝包,內容會包含多個 modules 或其他相依關係的 packages。
立相依關係的應用
以下步驟為透過一個乾淨新專案為設計起點:
- 先建立一個 test.js 等待(被 init 被偵測)
- 透過 init 來建立 package 並手動設定 package 基本資料。過程中你可以直接 Enter 用 npm 預判的預設值來自動填入。
L:\nodeDemo>npm ls |
接著安裝 npm express 到這個本地安裝目錄內,並添加 --save
儲存在相依關係清單。
L:\nodeDemo>npm install express --save |
此時你可以回到根目錄的 package.json 內查看 dependencies 部分,會寫到這個模組需要依賴 express 套件。未來如果別人從 npm 安裝你這個模組時,會自動安裝 express 使你的自訂模組正常運行。你也可以試著從 /node_moudles/express/package.json
查看 dependencies 資訊,代表這個模組也依賴很多別人的套件。也就是為何你只安裝 express 卻在 /node_moudles/
被強迫載入一堆你不認識的模組套件。回話題到 test.js 設定 express 的調用:
const express = require('express'); |
回到終端機執行你的 test.js
L:\nodeDemo>node test.js |
分享模組至 npm
你可以將自建的 npm 模組分享至 npm 開源平台(需 註冊帳號),如需打造私用領域需額外 pro 付費。要發布 package 至 npm 開源平台時有以下主要指令:
- npm whoami
若已登入帳號,能看到帳戶資訊 - npm login
登入帳號 - npm publish
將目前路徑下的 package 上傳至 npm,並根據 package.json 基本資料自動提供名稱與版本資訊。如需版本更新需手動調整 package.json 的版本號並再次覆蓋的送出本指令。 - npm install <Package_Name>
將開源平台上的 Package 進行下載安裝
Core Modules (原生模組)
由 Node 提供原生的內建模組,提供直接使用於應用 APP 的操作。大致可分為以下常用重要的原生模組。
fs 檔案系統
fs 檔案系統可用功能十分廣泛,除了能對檔案進行調整讀取也能對目錄進行控制。使用前同樣需要透過 require 來載入該模組。
檔案資訊
透過 fs.stat()
方式獲得檔案情報,需要從 callback 的參數來獲得。不建議於進行檔案讀寫時額外用此方法確認之失敗手續,你應該直接用那些讀寫方法且若 err 則直接處理失敗手續。
const fs = require('fs'); |
檔案讀取
檔案 I/O 存取操作的相關類別、方法、事件。讀取檔案的行為本身又可以分為同步 fs.readFileSync(path[, options])
與非同步 fs.readFile(path[, options], callback)
:
/**********file:test.txt |
L:\nodeDemo>node test.js |
/**********file:test.txt |
L:\nodeDemo>node test.js |
寫入檔案
寫入檔案方式為 fs.writeFile(file, data[, options], callback)
,指定路徑若檔案不存在則新增,反之為覆蓋。
const fs = require('fs'); |
對已存在(不覆蓋)的檔案進行插入文字方式為 fs.appendFile(path, data[, options], callback)
const fs = require('fs'); |
刪除檔案與更名
刪除檔案的方式為 fs.unlink(path, callback)
,修改檔案名稱的方式為 fs.rename(oldPath, newPath, callback)
// const fs = require('fs'); |
開啟關閉的讀取修改
這裡跟前面不太一樣的是先透過開啟檔案方式 fs.open(path[, flags[, mode]], callback)
將資源列入暫存再進行讀取寫入。相對來說資源效率比前面的使用還好。開啟檔案方式需使用 標記 flag 來代表何種開啟方式,flag 公式如下:
‘r’: 打開檔案用於讀取。如果檔案不存在則異常。
‘r+’: 打開檔案用於讀取和寫入。如果檔案不存在則異常。
‘rs+’: 打開檔案用於讀取和寫入(同步模式)。IO 操作將繞過本地系統的檔案緩存。
‘w’: 打開檔案用於寫入。如果檔案不存在則創建,如果檔案存在則截斷。
‘wx’: 類似於 ‘w’,但如果路徑存在則失敗。
‘w+’: 打開檔案用於讀取和寫入。如果檔案不存在則創建,如果檔案存在則截斷。
‘wx+’: 類似於 ‘w+’,但如果路徑存在則失敗。
‘a’: 打開檔案用於追加。如果檔案不存在則創建。
‘ax’: 類似於 ‘a’,但如果路徑已存在則失敗。
‘a+’: 打開檔案用於讀取和追加。如果檔案不存在則創建。
‘ax+’: 類似於 ‘a+’,但如果路徑存在,則失敗。
‘as’: 打開檔案用於追加(同步模式)。如果檔案不存在則創建。
‘as+’: 打開檔案用於讀取和追加(同步模式)。如果檔案不存在則創建。
為了連續性作業示範,開啟檔案後我們將內容讀取動作 fs.read(fd, buffer, offset, length, position, callback),並最後將檔案關閉 fs.close(fd, callback)。
const fs = require('fs'); |
L:\nodeDemo>node test.js |
events 事件
events 為提供事件監聽與處理的方法,在 Node.js 的一些物件(透過 Emitter 觸發器)來觸發指定事件 (Listener) 進行呼叫函式。
const EventEmitter = require('events'); |
buffer 緩衝區
Buffer 為處理二進位資料時的物件,要求作業系統之記憶體空間配額。當使用 fs 檔案讀取時會用到存放處理,Buffer 在全域下不需要使用 require 就能使用。
const bf = Buffer.from('Loki', 'utf8'); |
path 路徑
path 主要是在進行 fs 檔案目錄之操作讀取寫入時,會遇到的字串轉換處理。
const path = require('path'); |
其他原生模組
以下是部分常見模組,還有很多模組無法一一列出。下一節將重要的網路應用相關模組(包含原生與第三方)額外獨立介紹。
- os:控制作業系統或獲得相關資訊的方法。
- url:提供解析 URL 之方法。
- querystring:用來解析從用戶端傳回 querystring 字串之方法。
- util:提供開發者使用的實用函式。
網路應用
本章節目標將常用之網路應用開發所需模組與設計技巧。
net 通訊服務
TCP 是 HTTP 通訊的基礎協定,作為 Server 與 Client 的封包傳送,用於一些底層上的資料確認通信使用。這不是網頁媒體使用,而只是一種資訊封包傳送技術。使用方式為透過 net 模組來進行架設 TCP 伺服器。我們需要設計兩個應用程式分別為 server.js 與 client.js,TCP 伺服器保持監聽模式等待 Client 進行連線進一步取得 Client 資料,並回應資料還給 Client。
const net = require('net'); |
const net = require('net'); |
接著透過不同 node 環境分別扮演 Server 與 Clinet 來測試連線。
http 網頁服務
HTTP 架構於前者 TCP 之上層協定,能作為 Web Server 與瀏覽器之間的資料傳送。我們能使用 http 模組來建立 Web Server 的相關類別、方法、事件(也是 NodeJS 的官方手冊初登場的新手教材教學),用於處理瀏覽器所發出的 HTTP 請求。
回應內容
當瀏覽器對伺服器進行 HTTP 請求時獲得文字回應方式:
- 指定資料夾並建立 js 檔案,準備以下內容:
webServerTXT.js const http = require('http'); //宣告原生模組 http
//建立伺服器且提供網頁狀態、HEAD 資訊、網頁內容
const server = http.createServer((request, response) => {
// response.statusCode = 200;
// response.setHeader('Content-Type', 'text/plain'); //設定 response HEAD
// response.write('Hello World') //設定網頁內容
// response.end(); // response 送出
// same as ↓
response.writeHead(200,{'Content-Type':'text/plain'}); //response.setHeader() 適合循序參數調整且最後將由 writeHead() 進行嘗試合併,writeHeader() 內容優先為主
response.end('Hello World');
});
//對伺服器進行開機並於完成作業後顯示文字至終端機
const hostname = '127.0.0.1', port = 3000; //設定 Web 主機與通訊埠
server.listen(port, hostname, () => {
console.log(`
Server running at http://${hostname}:${port}/
Close Server press 'Ctrl+C' to exit
`);
}); - 接著終端機輸入指令。透過提示的指令開啟瀏覽器至指定 URL 網頁得到提示與伺服器運作之真實網頁。
cmd L:\nodeTest>node webServerTXT.js
Server running at http://127.0.0.1:3000/
Close Server Use Key 'Ctrl+C'
我們也可以直接回應一個網頁格式的內容,這裡我們寫得更簡速些,另注意 HEAD 資訊改為 text/html
。
require('http').createServer((req, res) => { |
L:\nodeDemo>node webServerHTML |
也能傳送 JSON 資料給請求端,注意 HEAD 宣告改為 application/json
。
const http = require('http'); |
關於 request 用途, http.createServer
所產生的 callback 之中,除了能指定 response 內容作為我們的目標網頁,request 也能提供非常多有用的 IncomingMessage 來源資訊。譬如我們可以取得請求方的資料如 URL、HEAD、DATA
const http = require('http'); //宣告原生模組 http |
回應檔案
前面來說都是透過 JavaScript 來會應指定的文字,我們可以搭配 fs 模組取得內容進行回應。以下寫法為直接找到指定目錄,同時可以直接根據 URL 檔案名稱找到該目錄下的相同檔名,這裡多餘地利用 require('path').basename(req.url)
來過濾多餘路徑僅判斷檔名就好(不論 URL 的分類路徑)。以下前置先建立目錄 public 與 HTML 檔案 index.html
, a.html
, b.html
, page404.html
。
const http = require('http'); |
原生 HTTP 模組並不是很彈性,大部分的人都會推薦使用 npm 元件 express 作為網頁伺服器服務首選。
路由處理
路由是指透過 URL 請求路徑來判斷執行的檔案讀取,呈現一種靜態檔案與動態網頁內容的變化。舉例來說,可以根據請求的網址內容做不同的網址名稱(不需要副檔名)來找到指定的路徑檔案,以下前置與上節相同都有 public 目錄與數個檔案。:
const http = require('http'); |
表單提交
我們延續路由機制進行設計,提供三種路由可能分別是表單、結果、不存在。表單部分進行設計一個 URL 路由指向到靜態檔案 form 並採用 POST 方式傳送,並提交後導向到結果部分,其路由指定之路徑名稱 url。如果是其他非指定的路由或非 POST 方式都導向到 404 頁面去。先規劃 form.html
並放入指定目錄 plubic 內。
|
接著設計 node 應用,這裡多引用 require('querystring')
能幫助我們將表單資料(為二進位資料,需進行 toString 才能變成文字串資料)轉換成 JSON 格式。
const http = require('http'); |
Express.js
Express 是一個知名常用的網路應用程式開發框架,提供開發者更快有效建立 Web 應用程式且支援 MVC 架構。提供應用程式與 Web 之間完整的路由與中介軟體。
初次安裝
跟隨 Express 官方手冊教學,我們試著從 npm 方式創造一個應用程式。
- 於既定目錄下執行 npm init 初始化並填寫基本資訊,利用 init 來協助建立 package.json,過程中的參數事項可自行評估填寫或略過。
- 將 express 相依安裝到專案目錄。另外可添加
--save
參數進行安裝,這能幫我們自動安裝相依關係清單中的模組。
L:\nodeDemo>npm init |
初始運作與掛載靜態目錄
這裡試著用 express 產生一個可服務的 Node Web 運作,而 express 提供內建的中介軟體函式 express.static
來設定靜態路由,這能方便你掛載一些媒體檔案或網頁目錄。
- 規劃練習檔案路徑為
/formDir/post.html
,/formDir/delete.html
,/public/index.html
,內容隨意輸入能判別即可。 - 規劃以下 index.js 檔案,並於終端機輸入
node .
或node ./
即可執行嘗試並指定 URL 測試。這裡不用檔名是因為 index.js 命名技巧關係。
const express = require('express'); |
何謂 MVC
MVC 是一個設計上的物件導向模式之架構,並不是一個技術規範。將系統架構分類出 Model 資料模型、View 使用介面、Controller 控制邏輯三大核心元件。能很常的在一些 Web Applications 應用上被看到廣泛使用。
Web 開發之類型
- web Application
在 Web 開發領域內我們稱呼為 Web 應用,藉由使用者的 HTTP 請求內容,透過相關邏輯處理,在伺服器端生成使用者所需的資料畫面形成互動。像是常逛的網站都是這類型 Web 開發,譬如購物網站、網路銀行、拍賣、會員商務網站等。 - web Services
在 Web 開發領域內還有另一種稱呼為 Web 服務類型,透過 SOAP 協定或 RESTful API 方式進行資料交換,其實 Web 服務也是一種 Web 應用,只差在通常不是對直接對消費者的應用互動。像是 Google API、Ficker API、OpenData 等。
在開發 Web 應用時,傳統方式是從輸入、處理、輸出的步驟來完成互動設計。而 MVC 則是反將這些步驟拆解成三種元件,每當使用者使用瀏覽器提出 HTTP 請求時,由 Controller 收到請求進行執行動作,也就是控制 Model 與 View 的變化,而 Model 元件會負責將資料準備好使得 View 能順利生成 HTTP 回應。如圖所示:
- Model 資料模型
主要負責 Web 應用的資料存取與處理規則,也就是存取至資料庫、fs 檔案、XML 等相關資料。 - View 使用介面
展示邏輯的物件,也就是建立使用者的瀏覽執行資訊,而 HTTP 回應的資訊通常是 HTML 網頁。View 元件會透過 Mode 元件來取得資料並將資料轉換成給使用者的畫面資訊。 - Controller 控制邏輯
整個 Web 應用的中心處,負責跟 view 跟 Model 元件之間的協調與控制執行,例如在獲得使用者的 HTTP 請求處理就屬於 Controller 控制器範圍內,根據所需操作去控制 Model 來存取資料,然後送至 View 進行畫面所需的 HTML 網頁。
應用程式產生器
Express 提供一個需安裝的 產生器 能協助幫忙快速建立應用程式所需要的基本框架結構,透過該步驟協助能快速建立一個 Web 應用開發。
使用產生器來建立應用程式結構,只是多種用來建立 Express 應用程式結構的其中一種方式。您有權使用這種結構,或是加以修改盡量符合您的需求。
安裝
透過指令 express-generator -g
進行全域安裝(必要)。Express Generator 與前面介紹的 Express 不同的是,Express 可透過區域安裝方式再自行 require 載入使用,而 Express Generator 是直接全域安裝並透過指令輸入express <APP_NAME>
即可產生具備 MVC 架構的 APP 初始環境。因此選擇 Express Generator 來初始化應用即可,不用預先安裝 Express 到區域或全域上。
L:\nodeDemo>npm install express-generator -g |
初始化應用
由於現在已經有全域指令 express 能使用,直接在目前工作目錄上建立一個名為 lokiApp 的 Express 應用程式:
L:\nodeDemo>express lokiApp |
到現在你已經獲得一個應用程式目錄同時該目錄持有完整的 MVC 架構。接著根據提示我們需要轉到應用程式的目錄下,將所需要的相依模組安裝起來(將會自動生成 node_moudle 目錄與相關檔案)。
L:\nodeDemo>cd lokiApp |
最後,你可以試著執行 Web 服務起來。
L:\nodeDemo\lokiApp>SET DEBUG=lokiapp:* & npm start |
結構說明
現有資料上上可以分為三個重要目錄:
- public:放置靜態 HTML5、CSS、JS 檔案。路由已經設定好,任何放入此目錄下的檔案都能直接對應網址上使用。譬如
plubic/test.html
的 URL 位置為http://127.0.0.1:3000/test.html
。 - routes:路由模組
- views:MVC 的 View 目錄,由於是 Jade 樣板引擎所以副檔名皆為
.jade
,預設會提供 error.jade, index.jade, layout.jade 三個樣板檔案
以及兩個重要檔案:
- app.js:用於 Express.js 運作 Web 的核心檔案。主要是將 Expres 相關環境連結搞定。以下附上註解部分說明。
app.js var createError = require('http-errors');
var express = require('express'); //載入 Expres 模組
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
//載入路由目錄下的 index.js 與 users.js,作為新增路由使用
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express();//透過 app 拿來控制 Express 進行 set 與 use 相關設定。
// view engine setup
//設定 VIEW 樣板位置與引擎格式
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
//透過 use 來開啟網站功能,像是網站記錄、欄位資料分析等。
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public'))); //指定靜態目錄位置為 public
//套用路由到 Express 網站的應用程式內
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
//處理 404 沒有找到的錯誤
app.use(function (req, res, next) {
next(createError(404));
});
// error handler
//進行相關錯誤的處理過程
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
//最後使用 export 方式匯出整個 Express 物件的變數 app
module.exports = app; - package.json:相關定義檔案,包含版本號、資訊相依模組內容等。
- bin/www:建立 Web 服務的核心檔案,而預設埠 3000 也是在此設定。
bin/www var port = normalizePort(process.env.PORT || '3000'); //預設 port 號
app.set('port', port);
手動添加路由與 View 網頁
這裡我們將示範如何手動添加路由與 Jade 樣板引擎的 MVC 規劃的 View 頁面。首先需要對 app.js
進行載入路由與新增路由。
var indexRouter = require('./routes/index'); |
接著設計我們的 routes/custom.js
檔案(可以拿 index.js 來改)。
var express = require('express'); |
再來到 MVC 結構的 views 目錄新增 Jade 樣板。這裡我們要對應正確名稱 lokiJade.jade
extends layout |
JADE 可以分為兩大區域:
extend layout
:能幫忙擴展同目錄下 layout.jade 檔案。譬如 layout.jade 的內容就是常用的 html 宣告。layout.jade doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
block contentblock content
:為區塊替換,也就是layout.jade
與lokiJade.jade
的 block content 形成內容。
Jade 語法簡介:
=var
符號為完整指定參數之內容,也就是可指定上一步驟的 response 參數物件。如果是在某字串內替換則使用#{var}
方式來替換。- HTML 標籤指名稱沒有
<>
,如需指定 attr 屬性寫法為(key1=val1,key2=val2)
,例如a(style="color:red;font-size:2rem",href="/index")
。 - 指定 id 方式為
div#id
,指定 class 方式為div.class
即可,如此直白。
檢查畫面結果
最後,我們可試著重新執行一次 Ctrl+C,這次可嘗試不添加 SET DEBUG 直接輸入指令。並測試網址畫面 http://127.0.0.1:3000/custom
是否成功。
L:\nodeDemo\lokiApp>npm start |
當用戶瀏覽該網址時,Node.js 端的 Console 會看見相關 GET 動作,包含我們的路由目錄以及 CSS 樣式表。
GET /custom 304 184.458 ms - - |
與 MySQL 連線
Express 扮演伺服器角色時,我們能跟各種 資料庫 進行連線整合。舉例來說,在伺服器端安裝 MySQL 服務(這裡採 WINDOWS XAMPP 做簡單示範)並嘗試安裝模組進行連結。
事前準備 SQL,將以下 SQL 建立準備完畢,將獲得資料庫名為 node_sample 並持有 animal 資料表:
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; |
接著為了與 MySQL 連結我們需要安裝 mysql 模組,進行全域安裝後嘗試簡單使用 test.js 來進行測試。
const mysql = require('mysql'); |
接著指定路徑到檔案位置,直接透過 node 來執行該 js 應用程式(不是 Express 應用)。
L:\nodeDemo>node test.js |
Express 內使用 SELECT
沿用之前的 lokiApp 應用,我們將規劃一個路由作為顯示資料庫 SELECT 結果,並透過 jade 進行模板輸出。同樣的在那之前 mysql 模組已全域安裝完成。步驟修改處相同流程,先至 app.js 進行添加路由位置,未來只要網址輸入 /animal
則將指向到 /routes/animal
執行內容。
var animalRouter=require('./routes/animal'); /////////////////////////////custom SQL SELECT |
接著到 routes 目錄新增 animal.js 進行應用程式編寫,每當有人到達指定 URL 請求時進行以下作業:
var express = require('express'); |
const mysql = require('mysql'); |
由於 response 的 render 作業會去取得 animalJade.jade
樣板並持有變數 items。因此開始規劃網頁畫面結果:
extends layout |
此時嘗試網址 http://127.0.0.1:3000/animal
查看網頁結果。