ExpressJS (TypeScript)
單元:基礎概念與環境設定
主題:建立基本 TypeScript 設定(tsconfig.json)
簡介
在使用 ExpressJS 搭配 TypeScript 開發 API 時,tsconfig.json 是整個專案的「指揮中心」。它決定了編譯器如何解析、轉譯以及檢查程式碼,直接影響開發體驗與最終產出的品質。沒有正確的設定,常會碰到「找不到模組」或「型別不相容」等錯誤,浪費大量除錯時間。
本篇文章將從 為什麼需要 tsconfig.json、核心屬性的意義、實務範例 逐步說明,讓剛入門的開發者能在最短時間內建立一個 安全、可維護、符合 ExpressJS 工作流程 的 TypeScript 設定。
核心概念
1. tsconfig.json 的角色
tsconfig.json 是 TypeScript 編譯器 (tsc) 的 設定檔,位於專案根目錄。當執行 tsc 或 npm run build 時,編譯器會自動讀取此檔案,依照裡面的屬性決定:
- 哪裡是來源檔(
include、exclude、files) - 輸出目錄(
outDir) - 目標 JavaScript 版本(
target) - 模組解析方式(
module、moduleResolution) - 型別檢查的嚴格度(
strict、noImplicitAny)
掌握這些設定,就能讓 TypeScript 與 Express 的開發流程無縫結合。
2. 基本結構
以下是一個最小化的 tsconfig.json 範例,適用於 Node.js 14+、ESM 模組、Express 4.x 的專案:
{
"compilerOptions": {
"target": "ES2020", // 產出符合 Node 14+ 的語法
"module": "NodeNext", // 使用 ESM 解析(支援 .js/.ts 的混寫)
"moduleResolution": "NodeNext",
"outDir": "./dist", // 編譯後的檔案放在 dist 目錄
"rootDir": "./src", // 原始碼根目錄
"strict": true, // 開啟所有嚴格型別檢查
"esModuleInterop": true, // 讓 CommonJS 模組可使用 default 匯入
"skipLibCheck": true, // 加速編譯,略過宣告檔檢查
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts"], // 包含 src 目錄下的所有 .ts 檔
"exclude": ["node_modules", "dist"] // 排除第三方套件與編譯輸出
}
target、module、moduleResolution應根據 Node 版本 與 專案需求 調整。strict為 最佳實務,能在開發階段即捕捉潛在錯誤。
3. 常見的 compilerOptions
| 屬性 | 說明 | 建議值 |
|---|---|---|
target |
編譯後的 JavaScript 版本 | ES2020(Node 14+) |
module |
模組系統 | CommonJS(舊版)或 NodeNext(ESM) |
outDir |
輸出目錄 | ./dist |
rootDir |
原始碼根目錄 | ./src |
sourceMap |
產生 .map 檔,方便除錯 |
true |
removeComments |
移除註解以縮小檔案 | false(開發階段) |
noImplicitAny |
隱式 any 型別錯誤 |
true(strict 包含) |
strictNullChecks |
嚴格的 null/undefined 檢查 | true(strict 包含) |
paths / baseUrl |
設定別名,簡化 import 路徑 | 依專案需求設定 |
4. 進階設定:別名與路徑映射
在大型 Express 專案中,常會把路由、服務、模型分散在多層目錄。使用 別名 可以讓 import 更直觀:
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@routes/*": ["routes/*"],
"@controllers/*": ["controllers/*"],
"@models/*": ["models/*"]
}
}
}
對應的 Node 執行時,需要在 package.json 加入 type: "module",或在 Webpack / ts-node 中使用相同的 alias 設定,才能避免執行錯誤。
5. 範例檔案結構
my-express-app/
├─ src/
│ ├─ routes/
│ │ └─ user.routes.ts
│ ├─ controllers/
│ │ └─ user.controller.ts
│ ├─ models/
│ │ └─ user.model.ts
│ └─ index.ts // 入口檔
├─ dist/ // 編譯產出
├─ tsconfig.json
└─ package.json
在上述結構下,tsconfig.json 只要設定 rootDir: "./src"、outDir: "./dist",以及上述的 paths,即可在程式碼中這樣寫:
import { Router } from 'express';
import { getAllUsers } from '@controllers/user.controller';
const router = Router();
router.get('/users', getAllUsers);
export default router;
程式碼範例
範例 1:最小化 tsconfig.json(適合快速驗證)
{
"compilerOptions": {
"target": "ES2019",
"module": "CommonJS",
"outDir": "./dist",
"strict": true
},
"include": ["src"]
}
說明:只開啟最關鍵的屬性,適合新手在「Hello World」階段先跑通。
範例 2:完整的 Express + TypeScript 設定(含別名)
{
"compilerOptions": {
"target": "ES2020",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"sourceMap": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": "./src",
"paths": {
"@routes/*": ["routes/*"],
"@controllers/*": ["controllers/*"],
"@models/*": ["models/*"]
}
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}
說明:此設定支援 ESM、別名,同時保留
sourceMap方便除錯。
範例 3:在 package.json 中使用 ts-node 直接執行
{
"scripts": {
"dev": "ts-node-dev --respawn --transpileOnly src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
}
}
說明:
ts-node-dev會自動讀取tsconfig.json,開發時不需要先手動編譯。
範例 4:使用 paths 別名的實作
// src/controllers/user.controller.ts
import { Request, Response } from 'express';
import UserModel from '@models/user.model';
export const getAllUsers = async (req: Request, res: Response) => {
try {
const users = await UserModel.find();
res.json(users);
} catch (err) {
res.status(500).json({ message: 'Server error' });
}
};
註解:
@models/user.model會被 TypeScript 解析為src/models/user.model.ts,不需要寫相對路徑../../models/...。
範例 5:自訂 noEmitOnError 防止錯誤產出
{
"compilerOptions": {
"noEmitOnError": true,
"outDir": "./dist"
// 其他設定略
}
}
說明:開啟後,若編譯過程出現型別錯誤,
dist目錄不會產生任何檔案,避免將不完整的程式碼部署到生產環境。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方法 |
|---|---|---|
模組解析錯誤 (Cannot find module) |
常因 module、moduleResolution 與 paths 不一致造成。 |
確認 tsconfig.json 與 Node 執行環境(type: "module" 或 commonjs)使用相同的設定。 |
esModuleInterop 未開啟 |
import express from 'express' 會編譯錯誤。 |
在 compilerOptions 加入 "esModuleInterop": true,或使用 import * as express from 'express'。 |
strictNullChecks 忽略 |
產生 null/undefined 相關的執行時錯誤。 |
保持 strict 為 true,或至少開啟 strictNullChecks。 |
| 別名在執行時失效 | TypeScript 能解析別名,但 Node 執行時找不到檔案。 | 使用 ts-node、webpack、或在 package.json 中的 exports 設定對應別名;或在 dist 目錄使用 module-alias 套件。 |
過度放寬 skipLibCheck |
雖能加速編譯,但可能隱藏第三方套件的型別錯誤。 | 只在大型專案的 CI 階段暫時關閉,平時保留 true 以提升開發效率。 |
最佳實踐
- 始終開啟
strict:即使初期會有較多錯誤,也能在開發早期養成良好型別習慣。 - 分離
src與dist:避免把編譯後的檔案納入版本控制(.gitignore中加入dist/)。 - 使用
sourceMap:配合nodemon或ts-node-dev,讓 stack trace 直接指向原始 TypeScript 檔案。 - 在 CI 中加入
tsc --noEmit:確保每次提交都通過型別檢查。 - 別名統一管理:所有路徑別名都放在
tsconfig.json,避免在程式碼中硬編碼相對路徑。
實際應用場景
微服務 API
- 多個服務共用同一套 型別定義(例如
UserDto)。透過tsconfig.json的paths,把共用型別放在packages/shared/src,各服務只要設定別名即可直接import { UserDto } from '@shared/dto'。
- 多個服務共用同一套 型別定義(例如
Serverless Functions
- 使用
target: "ES2022"、module: "CommonJS",讓編譯出的程式碼能在 AWS Lambda 或 Vercel 的 Node 執行環境直接跑。noEmitOnError確保只有通過型別檢查的程式碼被部署。
- 使用
開發環境熱重載
npm run dev使用ts-node-dev,在tsconfig.json中開啟sourceMap與experimentalDecorators(若使用 class‑validator),即可在修改路由或中介層時即時看到型別錯誤與程式變更。
總結
tsconfig.json 雖然只是一個 JSON 檔,但它是 TypeScript + Express 專案成功的基礎。透過正確的 目標版本 (target)、模組系統 (module)、以及 嚴格型別檢查 (strict),開發者可以:
- 立即在編譯階段捕捉潛在錯誤,提升程式品質。
- 使用 別名 (
paths) 簡化匯入路徑,讓程式碼更易讀、易維護。 - 配合
sourceMap、noEmitOnError等選項,確保除錯友好、部署安全。
只要依照本文提供的 範例 與 最佳實踐 設定 tsconfig.json,即能在 Express 中快速建立 型別安全、可擴充 的 API 專案,為後續的功能開發、測試與部署奠定穩固基礎。祝開發順利,玩得開心!