本文 AI 產出,尚未審核

ExpressJS (TypeScript)

單元:基礎概念與環境設定

主題:建立基本 TypeScript 設定(tsconfig.json


簡介

在使用 ExpressJS 搭配 TypeScript 開發 API 時,tsconfig.json 是整個專案的「指揮中心」。它決定了編譯器如何解析、轉譯以及檢查程式碼,直接影響開發體驗與最終產出的品質。沒有正確的設定,常會碰到「找不到模組」或「型別不相容」等錯誤,浪費大量除錯時間。

本篇文章將從 為什麼需要 tsconfig.json核心屬性的意義實務範例 逐步說明,讓剛入門的開發者能在最短時間內建立一個 安全、可維護、符合 ExpressJS 工作流程 的 TypeScript 設定。


核心概念

1. tsconfig.json 的角色

tsconfig.json 是 TypeScript 編譯器 (tsc) 的 設定檔,位於專案根目錄。當執行 tscnpm run build 時,編譯器會自動讀取此檔案,依照裡面的屬性決定:

  • 哪裡是來源檔includeexcludefiles
  • 輸出目錄outDir
  • 目標 JavaScript 版本target
  • 模組解析方式modulemoduleResolution
  • 型別檢查的嚴格度strictnoImplicitAny

掌握這些設定,就能讓 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"]   // 排除第三方套件與編譯輸出
}
  • targetmodulemoduleResolution 應根據 Node 版本專案需求 調整。
  • strict最佳實務,能在開發階段即捕捉潛在錯誤。

3. 常見的 compilerOptions

屬性 說明 建議值
target 編譯後的 JavaScript 版本 ES2020(Node 14+)
module 模組系統 CommonJS(舊版)或 NodeNext(ESM)
outDir 輸出目錄 ./dist
rootDir 原始碼根目錄 ./src
sourceMap 產生 .map 檔,方便除錯 true
removeComments 移除註解以縮小檔案 false(開發階段)
noImplicitAny 隱式 any 型別錯誤 truestrict 包含)
strictNullChecks 嚴格的 null/undefined 檢查 truestrict 包含)
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) 常因 modulemoduleResolutionpaths 不一致造成。 確認 tsconfig.jsonNode 執行環境(type: "module"commonjs)使用相同的設定。
esModuleInterop 未開啟 import express from 'express' 會編譯錯誤。 compilerOptions 加入 "esModuleInterop": true,或使用 import * as express from 'express'
strictNullChecks 忽略 產生 null/undefined 相關的執行時錯誤。 保持 stricttrue,或至少開啟 strictNullChecks
別名在執行時失效 TypeScript 能解析別名,但 Node 執行時找不到檔案。 使用 ts-nodewebpack、或在 package.json 中的 exports 設定對應別名;或在 dist 目錄使用 module-alias 套件。
過度放寬 skipLibCheck 雖能加速編譯,但可能隱藏第三方套件的型別錯誤。 只在大型專案的 CI 階段暫時關閉,平時保留 true 以提升開發效率。

最佳實踐

  1. 始終開啟 strict:即使初期會有較多錯誤,也能在開發早期養成良好型別習慣。
  2. 分離 srcdist:避免把編譯後的檔案納入版本控制(.gitignore 中加入 dist/)。
  3. 使用 sourceMap:配合 nodemonts-node-dev,讓 stack trace 直接指向原始 TypeScript 檔案。
  4. 在 CI 中加入 tsc --noEmit:確保每次提交都通過型別檢查。
  5. 別名統一管理:所有路徑別名都放在 tsconfig.json,避免在程式碼中硬編碼相對路徑。

實際應用場景

  1. 微服務 API

    • 多個服務共用同一套 型別定義(例如 UserDto)。透過 tsconfig.jsonpaths,把共用型別放在 packages/shared/src,各服務只要設定別名即可直接 import { UserDto } from '@shared/dto'
  2. Serverless Functions

    • 使用 target: "ES2022"module: "CommonJS",讓編譯出的程式碼能在 AWS Lambda 或 Vercel 的 Node 執行環境直接跑。noEmitOnError 確保只有通過型別檢查的程式碼被部署。
  3. 開發環境熱重載

    • npm run dev 使用 ts-node-dev,在 tsconfig.json 中開啟 sourceMapexperimentalDecorators(若使用 class‑validator),即可在修改路由或中介層時即時看到型別錯誤與程式變更。

總結

tsconfig.json 雖然只是一個 JSON 檔,但它是 TypeScript + Express 專案成功的基礎。透過正確的 目標版本 (target)模組系統 (module)、以及 嚴格型別檢查 (strict),開發者可以:

  • 立即在編譯階段捕捉潛在錯誤,提升程式品質。
  • 使用 別名 (paths) 簡化匯入路徑,讓程式碼更易讀、易維護。
  • 配合 sourceMapnoEmitOnError 等選項,確保除錯友好、部署安全。

只要依照本文提供的 範例最佳實踐 設定 tsconfig.json,即能在 Express 中快速建立 型別安全可擴充 的 API 專案,為後續的功能開發、測試與部署奠定穩固基礎。祝開發順利,玩得開心!