Node Express
Node Express with TypeScript
首先來做後端服務器架設,選了自己比較熟悉的 JS (Nodejs) 搭配 Express 框架。
沒想到,差點死在 TypeScript 跟 Node 不合上,還好看到這篇文章以及搭配官方,謝謝你火箭超人。
會想用 TS,主要是真的很好 debug 跟防止一些莫名的錯誤。
https://blog.logrocket.com/how-to-set-up-node-typescript-express/
https://nodejs.org/en/learn/typescript/run
Express
Express 相當輕量、自由度很高,但以官方 doc 對我來說,其實很不足,看完也不清楚應該怎麼去實作整體架構。所以我參考很多其他文章以及 NestJS 框架。
https://hackmd.io/@Heidi-Liu/note-be201-express-node
Install
$ pnpm i express dotenv
$ pnpm i -D typescript @types/express @types/node
The DotEnv package is used to read environment variables from a .env file.
ESmodule 跟 CommonJS
由於 CommonJS 是 Nodejs 原本在使用的模組系統,因此適配性比較高,但要使用 TS 開發的話,必須用 ESmodule。不過 Nodejs 本身不直接支援執行 TS 檔案,他不認識 TS 也不認識 import (ESmodule)。

所以我們要安裝 ts-mode 協助。安裝後,改成用 npx ts-node 執行就可以
npx ts-node index.ts
ts-nodets-node 實際做的事:在開發、執行的時候,即時把 TS 編譯成 JS,這樣 Node.js 就可以解析。
也要注意 tsconfig.json 的定義會影響 ts-node 的編譯方式,包含下面會提到 path。
以下是 chat 提供常見的設置 for Node.js,跟開發 React 時有點不同。
{
"compilerOptions": {
"target": "ESNext", // 指定編譯後的 JavaScript 版本
"module": "CommonJS",
"moduleResolution": "node", // 模組解析方式,支持 Node.js 方式解析模組
"esModuleInterop": true, // 開啟與 CommonJS 的互操作性
"strict": true, // 開啟 TypeScript 的嚴格型別檢查
"skipLibCheck": true, // 跳過庫文件的型別檢查,提升編譯速度
"resolveJsonModule": true, // 允許引入 `.json` 文件
"baseUrl": ".", // 設定基本路徑
"paths": {
"@models/*": ["src/models/*"],
"@routes/*": ["src/routes/*"]
},
"outDir": "./dist", // 編譯後的輸出目錄
"rootDir": "./src" // 原始 TypeScript 源碼所在的根目錄
},
"include": ["src/**/*"], // 包含需要編譯的檔案
"exclude": ["node_modules"] // 排除不需要編譯的檔案
}
雖然可以 run server 了,但馬上就會發現沒辦法根據 file changed hot reload,再安裝 nodemon,一個可以監控檔案改變的工具,改成用 nodemon 執行腳本。因為nodemon會自動偵測是否有ts-mode,有的會會預設使用ts-mode, shell script 就不用再給 ts-mode。
nodemon -L index.ts
nodemonNodemon 預設會監聽所有檔案,如果應用很大,可以創建 nodemon.json 定義需要監聽的檔案。
{
"watch": ["src"], // 僅監聽 src 底下的檔案
"ext": "ts,js,json", // 觸發的檔案類型
"ignore": ["node_modules", "dist"]
}
要注意的是,即使用 nodemon 也是自動更新 server,所以在 client (browser page) 仍要手動 refresh 才會是新資料。
Path Alias
我們通常習慣在 tsconfig 裡設置檔案路徑的縮寫,來節省 import 時的路徑長度。由於 Node.js 沒辦法解析 TS 的這般操作,要安裝 tsconfig-paths,再把 script 加上路徑註冊。
-r 是 Node.js 提供的 flag,表示 node 執行程式碼前,先 require 模組。
nodemon -r tsconfig-paths/register -L index.ts
不過 tsconfig-paths 僅協助開發時的環境,若要編譯要安裝 tsc-alias,並把 build 指令改為。
tsc --project tsconfig.json && tsc-alias -p tsconfig.json
async ts error
目前仍無法解掉 - 關於 async 語法過於新,沒辦法在 Node.js 環境中的頂層使用。試過以下還是沒得解,一但在 package.json 加上 type:Module 就會無法解析 ts 檔案 Unknown file extension ".ts"。
https://stackoverflow.com/questions/64610476/nodejs-typescript-ts-node-dev-top-level-await
暫時就只能先不把 await 使用在檔案最頂層
File System
上面跟 TS 相關的設置完成後,終於可以進入正式架設 Express Server 的環節。 架構參考了 NestJS 的方式。
folders/
Routes -- 定義 API 路由,負責將特定路由請求導向對應的 controller
Controllers -- 處理 HTTP 請求及回應,通常調用 Services 的邏輯
Services -- 業務邏輯、與第三方交互、通用的工具,若要調用 DB,會引入 Models 的 function -> 在 NestJS 裡是 Providers
Models -- 定義資料數據結構,基於我們在 DB 的結構,並負責跟 DB 溝通
Entry
import express, { Express, Request, Response } from 'express';
import mainRoutes from '@routes/index';
import tourRoute from '@routes/tour';
import { getTeams } from '@models/teams';
const app = express();
const PORT = process.env.NODE_ENV === 'development' ? 3001 : 3002;
app.use(mainRoutes); // 把全部路由註冊進來,這個路由也包含 API
app.listen(PORT, function () {
console.log(`App is running on port http://localhost:${PORT}`);
});
app.use 的作用「註冊 Middleware 或 Route 於指定路徑」,沒指定默認根路徑 /,分為全局、指定
// 全局 不止定時,適用所有請求
app.use((req, res, next) => {
console.log(`Request URL: ${req.url}`);
next(); // 傳遞控制權給下一個中間件
});
// 指定 只有 /api 開頭的路徑才會走下面的邏輯
app.use('/api', (req, res, next) => {
console.log('API middleware');
next();
});
Database (MySQL)
這次資料庫選擇 MySQL,沒有特別原因,因為以前用過,雖然考慮過要做聊天室,是否用 firebase firestore 比較方便,但基於其他資料類型在關聯式可能比較好控制,所以最後仍選擇 MySQL。
本來想練習 SQL Queries,但考量到安全,還是用了 ORM,這個套件 Prisma 可以在 Node 環境搭配 TS 使用。
如果過往都以前端開發為主,第一次用 Prisma 可能會不習慣,因為他跟前端常見的 lib 不太一樣,前端 lib 通常是安裝後開箱即用,因為大多數是框架或者函數工具,但 Prisma 屬於「開發工具」,因此還要安裝 CLI。如果把 Prisma 安裝在全域就不用另外再安裝 CLI,但最好是不要這樣,讓各自專案處理自己 Prisma 以及 .env 比較好。
Prisma 的行動都必須透過 CLI 來操作,並不是 file update saved 後會自動執行。詳細的說明可以參考官方 Data modeling with Prisma ORM 這篇,更容易了解為什麼要用 ORM、以及大致的作用。
比起直接使用 SQL 的缺點,就是要多理解 Prisma 的語法,但我覺得安全性更重要。
Prisma 簡介
簡易版的安裝及初始使用參考官方這篇
pnpm add -D prisma
pnpm dlx prisma
# 安裝後執行下面這行可以查看 cmd list
npx prisma
# 開始使用 Prisma
npx prisma init
# init 後,會提醒接下來的步驟,基本上是
# 1. Set the DATABASE_URL in the .env file to point to your existing database.
# 2. Set the provider of the datasource block in schema.prisma to match your database: postgresql, mysql, sqlite...,etc.
# 3. Run prisma db pull -> 把資料庫原有的結構拉到 Prisma
# 4. Run prisma generate to generate the Prisma Client. -> 我們實際操作資料透過 Prisma Client
重點補充
DATABASE_URL 根據官網格式要求
Prisma 有個 Pulse 功能,可能可以拿來做聊天室。等要開發聊天室時來研究
https://www.prisma.io/docs/orm/overview/beyond-prisma-orm#build-real-time-event-driven-applications-with-prisma-pulse
礙於篇幅,實際的 Prisma 運用在 Node.js 寫在這篇。
express body
express 會需要用 body-parser 這個庫,才能解析 body 的內容。
雖然 4.x版本開始可以直接用下面這個方式,可以參考官方
app.use(express.json());
不過官方更新的 5.x 版本的說明卻用 body-parser,不太清楚其中差別,等之後再來研究。