Node.js Express 初入門 – 上集

Express 可以說是 Node.js 中最流行的 Web 開發框架,不但有著簡潔靈活的特性、還有豐富的文件和完整的 API Reference,令開發者能快速地搭建一個完整功能的網站。

今天就讓我們來為介紹 Node.js Express 的常用功能,包括 Express 路由機制 (Routing)、Express 靜態網頁伺服器 (Static Web Server) 如何載入圖片、文本等靜態檔案到瀏覽器上。

 

■ 使用工具: 編輯器、Node.js 終端機與瀏覽器

在學習Express的過程中,我們主要會用到三項工具──編輯器 (Editor)、終端機 (Terminal, 或稱命令行Command Prompt) 與瀏覽器 (Browser)。

這邊我們使用的編輯器為 Atom,當然你也可以用 Vim、Sublime 等你喜歡的編輯器。

還沒有安裝 Node.js 的讀者可以去 Node.js 的官網下載,安裝完成後開啟 Node.js 的 Command Prompt,輸入 node –version (或node -v) 後確認安裝成功。

接下來,讓我們從頭開始創建一個 node 專案。在桌面建立一個叫做 express 的資料夾作為專案名稱(或任何你想要的名字)。建立完後在資料夾中運行 npm init、將該資料夾轉成一個 node 專案。

下列為輸入prompt的命令:

cd C:\Users\Lynn\Desktop
mkdir express
cd express
npm init

npm init 的動作會為此專案創建一些設定,並將這些資訊存在一個叫 package.json 的檔案中。

在 package.json 這個檔案中,使用者可以定義應用名稱 (name)、應用描述 (description)、關鍵字 (keywords)、版本號 (version)、應用配置 (config)、主頁 (homepage)、作者(author)、版本庫 (repository)、bug的提交地址 (bugs)、授權方式(licenses)… 等。

cd 到 Desktop 後,創建一個叫 express 的資料夾,再 cd 到 express 中輸入 npm init。接下來會有一些關於這個 node 專案的初始設定。

設定完成後輸入 ls 展開資料夾內所有的檔案 (Windows指令為 dir),可以發現 express 資料夾中出現 package.json 這個檔案了。

Windows Prompt使用者記得是用dir而不是用ls喔= =”

 

■ 安裝Express

太好了,接下來讓我們試試在express中initialize Express這個模組吧!

npm install express --save
npm install express-generator -g

除了 install express 之外、還需在後面加上「–save」,這是什麼意思呢?在 npm 中,「Dependency」(依賴模組)是一個非常重要的概念,–save 的意思是將這個 package 設定為本次專案的 dependency。

事實上,所謂的 npm 意即node package manager、node 套件管理器,npm 的默認設定是將一個套件 (package) 安裝在 node 模組下。

故當我們在安裝其他人寫的 package 後,必須再設定這個 package 是這個專案的 dependency (依賴模組),告訴這個專案說:這次的專案需要依賴於這個模組才能運作噢!

 

■ 建立Express App – 設定伺服器(Server)與路由(Route)

為了將從頭建立起一個 Express App,接下來我們會設定應用伺服器 (Application Server) 和一個基本的路由 (Route)。

Routing 是把網址 (URL) 轉給特定程序處理的過程,幫助伺服器解析 URL、判斷 URL 路徑與相應的執行動作。

從使用者的角度來看,一個 Route 就是一個 URL,比如 lyrasoft.net/tw/about lyrasoft.net/tw/home

URL 之 所以被稱為 Route,是因為它告訴使用者如何從伺服器拿到資料的路徑。

從應用程式 (applications) 的角度來看,Route 會告訴應用程式什麼時候要做出什麼動作。舉例來說,/about 會告訴應用程式:「請載入About這個頁面的內容。」

被用來訪問一個網站的應用程式、通常稱為客戶(Client) 端,可以把我們的網頁瀏覽器 (Browser) 想成客戶端、而 Routes 就是輸入瀏覽器中的 URLs。

伺服器之所以會知道怎麼送和何時送資料到客戶端,是因為客戶端這邊向伺服器送過去一個 HTTP Request。Request 在這邊是一個專有名詞,當你將URL輸入瀏覽器時,實際上是瀏覽器會在這個URL的位址上送出一個Request給伺服器。

如果客戶端送了一個 Request、而伺服器端這邊有設定好回應的方法時,伺服器就會送回去一個 Response;若伺服器沒設定好 Response 的方法、就會回傳一個Error。

像是「Page Not Found」,表示伺服器找不到相對應的檔案或目錄。上頭顯示的 404,意指 HTTP 狀態碼 (Status Code),用以表示網頁伺服器 HTTP 回應狀態的 3 位數字代碼。2XX 代表伺服器成功接收到客戶端傳來的訊息、4XX 則代表 Client 端訊息有可能錯誤、讓伺服器無法處理。

讓我們來設定一個簡易的 Express.js 伺服器吧!首先用 const express、宣告 express 這個變數載入 express 函式庫,再宣告 app 變數為 express function、建立一個 Express 伺服器;最後告訴伺服器聽取 3000 這個 Port。

const express = require('express');
const app = express(); //建立一個Express伺服器
app.listen(3000); //告訴server聽取3000這個Port

簡單來說,Port 就像是電腦的門,一部電腦的 Port 編號從 0 到 65535,共有 65536 個、各有特定的用途。3000 是我們隨意選取的一個 Port,當然你也可以選擇其他編號,一般常用 3000、300x、8000、800x、8089、8080。

1000 以下由於已有大多系統在用,故通常不會使用;也有一些常見的程式會使用固定的 Port,如 MySQL 專門使用 3306,通常也會避開。

然後打開 Node.js Terminal,輸入 Node app.js 啟動 Node。

再打開瀏覽器、在 URL 上輸入 localhost:3000,就可以連線到伺服器。

為了之後更快地確認伺服器成功連線,讓我們在 app.listen 上加上一行訊息 “Example app is running on port 3000!”、並讓訊息在 console 上面印出來。

app.listen(3000, function () {
  console.log('Example app is running on port 3000!');}
);

重新啟動 app.js 檔案後就可以成功看到Console上的”Example app is running on port 3000!”了。這則訊息並不會讓客戶端的使用者看到、僅供開發者參考。

■ 自訂路由(Routing) – 接收請求(Get Request)與回傳回應(Send Response)

回去確認一下瀏覽器。咦?! 明明成功在Console上印出訊息了,伺服器怎麼會說Cannot Get呢? 很簡單,因為我們沒有告訴伺服器接收Request和回傳Response回去的方法阿~讓我們來建立一個路由函數:

function (req,res) { 
//接收連線請求、並回應客戶端
};

如何接收並回應HTTP Request有很多不同的方法,我們在這邊介紹Request最主要的一種方法:「Get Request」。

輸入新的一行程序,告訴伺服器在根目錄(/)時需要「Get」資料、並「Response」回送一行字串(String)回去。Node.js 就是 JavaScript 寫成,故字串同樣是用單引號(‘ ‘)包起來。

app.get('/', function (req, res) {
  res.send('Express is excellent!')
});

接下來在 Terminal 先按 ctrl-c 跳出執行程式、再一次輸入 node app.js  (注意每次都要記得這個動作),重新在瀏覽器輸入 localhost: 3000,我們可以發現伺服器果然回傳 “Express is excellent!”這個字串回來了!

讓我們來玩個有趣的嘗試,在Express is excellent!這行字串中加上<h1>Tag:

app.get('/', function (req, res) {
  res.send('<h1>Express is excellent!</h1>')
});

再一次啟動 app.js、重新整理瀏覽器,可以發現出現的字樣並不是<h1>Express is excellent!、而是加了HTML Header Tag效果的 “Express is excellent!”。

到底發生了什麼事情呢? 因為瀏覽器最初就是設計來展示 HTML,故瀏覽器在接收到伺服器這邊回傳的字串時,是把收到的 ”<h1>Express is excellent!</h1>”解析成 HTML 呈現出來。

● 小練習: 使用GET 回傳有包括 <h1>首頁</h1> 的 html

解答:

app.get('/', function(req, res) {
  res.send('<h1>首頁</h1>');}
);

 

■ 從URL拿到資料

還記得我們在前面舉例說,/about 會告訴應用程式:「請載入 About 這個頁面的內容。」嗎?

來試試看從 URL 的路徑中讓伺服器拿到資料吧。「/」代表從根目錄拿到資料、「/about」代表可以拿到 About 頁面,也可以往下繼續設定路徑找到 about 下的 about-me 頁面:「/about/about-me」。

app.get('/', function(req, res) {
    res.send('Send root!')
});
app.get('/about', function(req, res) {
    res.send('Send about page!')
});
app.get('/about/about-me', function(req, res) {
    res.send('Send about-me page!')
});

 

啟動 app.js、輸入設定好的 URL 路徑,可以看到果真將路徑下的資料傳回來了!

伺服器會從 URL 找到並回傳字串,包括 HTML、JSON 等都是字串的一種。這次在「/blog」路徑下回傳一個 JSON 回應吧。

app.get('/blog', function(req, res) {
res.json({
text: 'Send blog page!'
});
});

好玩吧,以下是這次示範的程式碼,來試試看吧!

■ 從req.params拿到參數

我們可以進一步在URL做一些設定、並從 URL 拿到我們要的參數;這些參數的命名可由我們自己命名。首先介紹的 req.params,當伺服器拿到「/post/:id」這個路徑時,回傳id這個參數(parameter)、寫作「req.params.id」。

app.get('/post/:id', function(req, res) {
  res.send(req.params.id);}
);

這時在 URL 輸入/post/(我們設定的參數),就是 /post/:id;比如輸入 /post/12345、req.params.id 就是 12345。 來看看瀏覽器上會出現什麼吧!

req.params 會將命名過的參數以 key-value 的形式存放;比如路徑 /user/:name,name 屬性會存放在 req.params.name。

我們也可以在中間加上字串、並結合兩個參數:

app.get('/about/:name/:nickname', function(req, res) {
  res.send(req.params.name + ' is so ' + req.params.nickname);
});

雖然在URL輸入/about/(我們設定的參數)/(我們設定的第二個參數),比如這邊的舉例是 /about/Lynn/pretty 因為中間加了一個字串將兩個參數串在一起,可以發現瀏覽器的結果會有些不同呢!

同樣附上示範程式碼,來試試看吧!

● 小練習:

(1) GET /api/users/1 回傳 json ;

{
 "id": 1,
 "name": "Joe",
 "age": 18
}

 

(2) GET /api/users/1 回傳 json

{
 "id": 2,
 "name": "John",
 "age": 22
}

 

解答:

app.get('/api/users/:number', function(req, res) {
    var a = {
        "id": 1,
        "name": "Joe",
        "age": 18
    };
    var b = {
        "id": 2,
        "name": "John",
        "age": 22
    };
    if (req.params.number == 1) {
        res.json(a);
    } else if (req.params.number == 2) {
        res.json(b);
    } else {
        res.send('NOT FOUND');
    }
});

 

■ 從req.query拿到參數

透過 req.query 拿到 URL 路徑 ?後面的參數資料,? 後面開始是 JSON 的 key-value 設定。讓我們回傳一個 req.query 參數:

app.get('/hell', function(req, res) {
    res.send(req.query);}
);

然後在 URL 上輸入/hell?(我們設定的key)=(我們設定的value),舉例為/hell?children=100。

既然說 ? 後面接的是 JSON 的 key-value 設定,那如果我們回傳的就是 JSON 格式呢? 試試看 res.json(req.query)

 

app.get('/gogo', function(req, res) {
  res.json(req.query);}
);

同樣在URL上輸入一樣的格式、? 後面接我們要的參數,舉例為 /gogo?shopping=5,我們可以發現結果是一樣的:

這是為什麼呢? 這是因為在傳送 JavaScript 物件(object)  或陣列 (array) 時, res.send 和 req.json 的結果是相同的。

那我們可以同時利用 req.params 和 req.query 、在 URL 上拿到資料嗎?

由於 JSON format 無法與一般字串接在一起、會印不出來,因此要先把 JSON 轉成字串 (stringify) 再印出來:

app.get('/hey/:yo', function(req, res) {
  res.send(req.params.yo + JSON.stringify(req.query));
});

 

在 URL 中輸入 /hey路徑、設定 req.params.yo 的參數為 my_dear,再設定 ?name=lynn ,來看看結果會是如何吧:

附上案例程式碼:

● 小練習:GET /api/query?x=123&y=234 把 query 變成 json 傳回去

{
  "x": "123",
  "y": "234"
}

 

解答:

app.get('/api/query', function(req, res) {
res.json(req.query);}
);

 

下一集,將為大家持續介紹如何使用 express 的 router 來幫助我們設定路由、使用 nunjucks 模板引擎、使用 body-parser,與 middleware 概念等詳細解說。