前一篇我們嘗試用 Node.js 建立了 HTTP API,我相信你應該學到相當的多,但是實戰上來講,我們並不會直接使用 Node.js 來建立 HTTP API,因為這樣會太麻煩了,因此我們會使用一些框架來幫助我們建立 HTTP API,而這邊我們就來介紹一個非常熱門的框架,也就是 Express.js。
什麼是 Express.js(又稱 Express) 呢?如果你本身具有前端開發經驗的話,你可以把它想像成是 Vue & Angular 為什麼呢?因為這兩個定位與 Express.js 非常相似,它們都是一個 Web 框架,只是 Vue & Angular 是用來開發網頁前端的,而 Express.js 則是專門針對 Node.js 的伺服器開發的框架。
Note
由於 React 在官方定義上是 Library,因此才沒有提到 React,但其實 Library 跟 Framework 的界線越來越模糊就是了。
Express 目前是廣泛被利用的框架,舉例來講...Paypal 與 Uber 就有使用到 Express.js 來開發伺服器,由此可知,我們可以知道 Express 是一個非常成熟的框架,因此我們可以放心的使用它來開發伺服器。
當然,除了 Express 還有其他替代品可以選擇,如:Koa.js、Fastify、NestJS 等等,但這一篇我們會比較著重於使用 Express 與介紹 Express 的相關知識,至於其他框架的部分...我們有緣再來介紹 :D
起手式請你依照以下指令來建立專案
mkdir express-example
接著請你進入專案中
cd express-example
請別忘了初始化專案
git init
npm init -y
接著請你安裝 Express
npm install express
Note
這邊我們使用npm install express
來安裝 Express,但是你也可以使用npm install express --save
來安裝,這兩個指令結果是相同的,因為--save
是預設的,因此你可以省略;甚至你可以簡寫成npm i express
,因為i
是install
的縮寫。
接下來我們要來建立一個我們的第一支檔案,也就是 index.js
touch index.js
建立好後你可能會想說我們第一行應該是 const http = require('node:http');
對吧?但是這邊我們不會這樣做,因為我們要使用 Express,因此我們會使用 require('express')
來引入 Express
const express = require('express');
接著我們會需要將這個 Express 套件呼叫出來,因此我們會將 express()
呼叫出來,並且將它指派給 app
這個變數
const express = require('express');
const app = express();
接著我們會需要一個伺服器,因此我們會使用 app.listen()
來建立伺服器,並且指定 3000
這個 port,這邊我先順便補個簡單的 HTTP API,讓你可以在瀏覽器上看到結果
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello, World!');
});
app.listen(3000)
接下來你可以嘗試運行專案了,不意外你應該是可以在瀏覽器上看到「Hello, World!
」。
「疑?就這麼簡單?」
沒錯,就是這個簡單,恭喜你體驗到 Web Framework 的威力了!我們這就是人家常在講的...
「站在巨人的肩膀上,你可以看得更遠。」
那麼我們可以相比一下我們前面寫的程式碼與使用 Express 後的程式碼
純 Node.js:
const http = require('node:http');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
if (req.method === 'GET') {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ message: 'Hello, World!' }));
}
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
使用 Express:
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello, World!');
});
app.listen(3000)
透過兩者之間的比較,你應該也可以深刻的體會到為什麼我們要使用 Express 了,畢竟它幫我們省去了很多的麻煩,讓我們可以專注在開發上,而不是在處理一些瑣碎的事情。
那麼這邊我也直接示範一下把前面章節我們所寫的 TodoList API 改成 Express 版本:
const express = require('express');
const app = express();
const port = 3000;
const data = [];
app.get('/todos', (req, res) => {
res.send(data);
});
app.post('/todos', (req, res) => {
const { title, completed } = req.body;
data.push({
id: new Date().getTime(),
title,
completed,
});
res.send(data);
});
app.listen(3000)
但是當你改成這樣後,去嘗試戳你會發現 Post /todos
會無法正常運作,這是因為我們需要一個東西來幫助我們解析 req.body
,那該怎麼做呢?很簡單,補上 app.use(express.json());
就可以了
const express = require('express');
const app = express();
const port = 3000;
const data = [];
app.use(express.json());
// ... 略過其他程式碼
app.listen(3000)
這樣子你在戳 Post /todos
就可以正常運作了。
什麼是 Middleware 呢?你可以把它想像成中間人,以比較生活面的例子來講的話,你可以把它想像成你想要買房 or 租房,所以你會先經過仲介,然後仲介會幫你找到適合的房子,而這樣的過程就是中間層,而在程式碼上來講,你可以把它想像成一個函式,這個函式會在你的程式碼執行前,先執行這個函式,然後再執行你的程式碼,這樣的過程就是 Middleware。
房地產仲介幫助你找到適合的房子,就像 Middleware 在處理請求時,執行一些額外的操作,然後將處理權交給下一個處理或路由。
整個 Express 其實都是由 Middleware 的概念所組成的,因此 Middleware 是非常重要的一個概念。
回頭來講一下 app.use(express.json());
這個 Middleware,前面有提到這個 Middleware 會幫助我們解析 req.body
,讓我們可以正常取得資料,這邊讓我們來看一下它的原始碼:
// express/lib/express.js
var bodyParser = require('body-parser')
// ... 略過其他程式碼
exports.json = bodyParser.json
我們可以看到 app.use(express.json());
其實就是 app.use(bodyParser.json());
,只是被 Express 給封裝起來了而已,接著我們再往 body-parser
中看一下 json
的原始碼:
// body-parser/lib/types/json.js
// ... 略過其他程式碼
function json (options) {
var opts = options || {}
var limit = typeof opts.limit !== 'number'
? bytes.parse(opts.limit || '100kb')
: opts.limit
var inflate = opts.inflate !== false
var reviver = opts.reviver
var strict = opts.strict !== false
var type = opts.type || 'application/json'
var verify = opts.verify || false
if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
}
// create the appropriate type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type
function parse (body) {
if (body.length === 0) {
// special-case empty json body, as it's a common client-side mistake
// TODO: maybe make this configurable or part of "strict" option
return {}
}
if (strict) {
var first = firstchar(body)
if (first !== '{' && first !== '[') {
debug('strict violation')
throw createStrictSyntaxError(body, first)
}
}
try {
debug('parse json')
return JSON.parse(body, reviver)
} catch (e) {
throw normalizeJsonSyntaxError(e, {
message: e.message,
stack: e.stack
})
}
}
return function jsonParser (req, res, next) {
if (req._body) {
debug('body already parsed')
next()
return
}
req.body = req.body || {}
// skip requests without bodies
if (!typeis.hasBody(req)) {
debug('skip empty body')
next()
return
}
debug('content-type %j', req.headers['content-type'])
// determine if request should be parsed
if (!shouldParse(req)) {
debug('skip parsing')
next()
return
}
// assert charset per RFC 7159 sec 8.1
var charset = getCharset(req) || 'utf-8'
if (charset.slice(0, 4) !== 'utf-') {
debug('invalid charset')
next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
charset: charset,
type: 'charset.unsupported'
}))
return
}
// read
read(req, res, next, parse, debug, {
encoding: charset,
inflate: inflate,
limit: limit,
verify: verify
})
}
}
// ... 略過其他程式碼
這邊很複雜沒有錯,但真正整個核心是在 return function jsonParser (req, res, next) { ... }
這個函式,這個函式會在 req
之前執行,這過程中,就會依照設定來解析,例如:limit
、inflate
、reviver
、strict
、type
、verify
等等,當解析完畢後,就會將解析後的 JSON 資料附加到請求物件的 body 屬性中供後續路由處理函式使用。
看完一個超級複雜版本的 Middleware 後,我們來試著自己的 Middleware 吧?
建立自己的 Middleware 其實並不困難,你只需要建立一個函式,並且在函式中呼叫 next()
就可以了
const express = require('express');
const app = express();
const myMiddleware = (req, res, next) => {
console.log('myMiddleware!');
next();
};
app.use(myMiddleware);
app.get('/', (req, res) => {
res.send('Hello, World!');
});
app.listen(3000)
是不是很簡單呢?當你試著戳 http://localhost:3000
時,你會發現終端機中會印出 myMiddleware!
!
透過這個概念,以及總結前面的知識,其實我們是可以自己寫一個 JSON 的解析。
我們純 Node.js 的時候,其實是可以透過 req.on('data', (chunk) => { ... })
來解析 JSON 的,因此我們可以透過這個概念來實作一個 JSON 解析的 Middleware
const express = require('express');
const app = express();
const jsonParserMiddleware = (req, res, next) => {
let data = '';
// 監聽數據流事件,並將數據流串接起來
req.on('data', (chunk) => {
data += chunk.toString();
});
// 監聽數據流結束事件,並將解析後的 JSON 資料附加到請求物件的 body 屬性
req.on('end', () => {
try {
// 將解析後的 JSON 資料附加到請求物件的 body 屬性
req.body = JSON.parse(data);
// 繼續執行後續的 Middleware 或路由處理函式
next();
} catch (error) {
// JSON 解析失敗,回傳錯誤訊息
res.status(400).json({ error: 'Invalid JSON data' });
}
});
}
app.use(jsonParserMiddleware);
app.post('/', (req, res) => {
res.send(req.body);
});
app.listen(3000)
透過自己所建立的 Middleware 我們就可以在 Post /
中取得 req.body
囉~
當然 Middleware 不只有可以做解析而已,在實戰上通常會搭配在身份驗證上,例如像是 Token 是否有過期、是否有權限等等,這些都可以透過 Middleware 來處理。
那麼這一篇也差不多要告一個段落了,所以最後我們就來總結一下 Middleware 的概念吧!
req
、res
、next
這三個參數next()
才能繼續執行後續的動作那麼這一篇我們就先到這邊結束囉~
前陣子使用 Nuxt3 的時候踩雷踩到快崩潰,還好 ChatGPT 救了我?!