前言 前一篇我們嘗試用 Node.js 建立了 HTTP API,我相信你應該學到相當的多,但是實戰上來講,我們並不會直接使用 Node.js 來建立 HTTP API,因為這樣會太麻煩了,因此我們會使用一些框架來幫助我們建立 HTTP API,而這邊我們就來介紹一個非常熱門的框架,也就是 Express.js。
什麼是 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
建立專案 起手式請你依照以下指令來建立專案
接著請你進入專案中
請別忘了初始化專案
接著請你安裝 Express
Note 這邊我們使用 npm install express 來安裝 Express,但是你也可以使用 npm install express --save 來安裝,這兩個指令結果是相同的,因為 --save 是預設的,因此你可以省略;甚至你可以簡寫成 npm i express,因為 i 是 install 的縮寫。
撰寫 Express 接下來我們要來建立一個我們的第一支檔案,也就是 index.js
建立好後你可能會想說我們第一行應該是 const http = require('node:http'); 對吧?但是這邊我們不會這樣做,因為我們要使用 Express,因此我們會使用 require('express') 來引入 Express
1 const express = require ('express' );
接著我們會需要將這個 Express 套件呼叫出來,因此我們會將 express() 呼叫出來,並且將它指派給 app 這個變數
1 2 const express = require ('express' );const app = express ();
接著我們會需要一個伺服器,因此我們會使用 app.listen() 來建立伺服器,並且指定 3000 這個 port,這邊我先順便補個簡單的 HTTP API,讓你可以在瀏覽器上看到結果
1 2 3 4 5 6 7 8 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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:
1 2 3 4 5 6 7 8 const express = require ('express' );const app = express (); app.get ('/' , (req, res ) => { res.send ('Hello, World!' ); }); app.listen (3000 )
透過兩者之間的比較,你應該也可以深刻的體會到為什麼我們要使用 Express 了,畢竟它幫我們省去了很多的麻煩,讓我們可以專注在開發上,而不是在處理一些瑣碎的事情。
那麼這邊我也直接示範一下把前面章節我們所寫的 TodoList API 改成 Express 版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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()); 就可以了
1 2 3 4 5 6 7 8 9 10 11 12 const express = require ('express' );const app = express ();const port = 3000 ;const data = []; app.use (express.json ()); app.listen (3000 )
這樣子你在戳 Post /todos 就可以正常運作了。
Middleware 什麼是 Middleware 呢?你可以把它想像成中間人,以比較生活面的例子來講的話,你可以把它想像成你想要買房 or 租房,所以你會先經過仲介,然後仲介會幫你找到適合的房子,而這樣的過程就是中間層,而在程式碼上來講,你可以把它想像成一個函式,這個函式會在你的程式碼執行前,先執行這個函式,然後再執行你的程式碼,這樣的過程就是 Middleware。
房地產仲介幫助你找到適合的房子,就像 Middleware 在處理請求時,執行一些額外的操作,然後將處理權交給下一個處理或路由。
整個 Express 其實都是由 Middleware 的概念所組成的,因此 Middleware 是非常重要的一個概念。
回頭來講一下 app.use(express.json()); 這個 Middleware,前面有提到這個 Middleware 會幫助我們解析 req.body,讓我們可以正常取得資料,這邊讓我們來看一下它的原始碼:
1 2 3 4 var bodyParser = require ('body-parser' )exports .json = bodyParser.json
我們可以看到 app.use(express.json()); 其實就是 app.use(bodyParser.json());,只是被 Express 給封裝起來了而已,接著我們再往 body-parser 中看一下 json 的原始碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 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' ) } var shouldParse = typeof type !== 'function' ? typeChecker (type) : type function parse (body ) { if (body.length === 0 ) { 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 || {} if (!typeis.hasBody (req)) { debug ('skip empty body' ) next () return } debug ('content-type %j' , req.headers ['content-type' ]) if (!shouldParse (req)) { debug ('skip parsing' ) next () return } 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 (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 建立自己的 Middleware 其實並不困難,你只需要建立一個函式,並且在函式中呼叫 next() 就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 const express = require ('express' );const app = express ();const jsonParserMiddleware = (req, res, next ) => { let data = '' ; req.on ('data' , (chunk ) => { data += chunk.toString (); }); req.on ('end' , () => { try { req.body = JSON .parse (data); next (); } catch (error) { 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 的概念吧!
Middleware 是一個函式
Middleware 會在路由處理函式之前執行
Middleware 必須傳入 req、res、next 這三個參數
Middleware 必須呼叫 next() 才能繼續執行後續的動作
Middleware 可以用來做身份驗證、解析資料、處理錯誤等等
那麼這一篇我們就先到這邊結束囉~
碎碎念 前陣子使用 Nuxt3 的時候踩雷踩到快崩潰,還好 ChatGPT 救了我?!
整理這些技術筆記真的很花時間,如果你願意 關閉 Adblock 支持我,我會把這份感謝轉換成更多「踩坑轉避坑」的內容給你!ヽ(・∀・)ノ
Advertisement