本文 AI 產出,尚未審核

ExpressJS (TypeScript) – 基礎概念與環境設定

主題:初始化專案 ── npm、ts-node、ts-node‑dev、nodemon


簡介

在使用 ExpressJS 開發 API 或 Web 應用時,若想同時享有 TypeScript 的型別安全與 Node.js 的執行效率,最關鍵的第一步就是把開發環境建好。
npm(Node Package Manager)負責管理套件、腳本與相依性;而 ts-nodets-node-devnodemon 則是讓我們在 TypeScript 原始檔上即時執行、熱重載的利器。
若環境設定不當,常會遇到「編譯慢、錯誤訊息難追蹤」或「每次修改都必須手動重啟」的痛點,直接影響開發效率與除錯體驗。本文將一步步說明如何正確初始化專案,並提供實務範例,幫助你在最短時間內建立 Express + TypeScript 的開發基礎。


核心概念

1. npm 初始化與套件安裝

# 建立一個全新的資料夾,並進入該目錄
mkdir my-express-app && cd my-express-app

# 初始化 npm,產生 package.json(-y 直接套用預設值)
npm init -y
  • package.json 會記錄專案名稱、版本、相依套件與自訂腳本,是 Node 生態系的核心檔案。

接著安裝 ExpressTypeScript 以及開發時需要的工具:

# 基本執行時相依
npm install express

# 開發時相依(-D 代表 --save-dev)
npm install -D typescript @types/node @types/express
npm install -D ts-node ts-node-dev nodemon
  • typescript:編譯器本身。
  • @types/...:提供型別定義,讓 TypeScript 能正確辨識 Node 與 Express 的 API。
  • ts-node:直接在 Node 執行環境中執行 .ts 檔案,省去手動 tsc 編譯的步驟。
  • ts-node-dev:在 ts-node 基礎上加入 熱重載(Hot Reload),檔案變動即自動重新執行。
  • nodemon:監控檔案變化,重新啟動 Node 程式,適合純 JavaScript 或需要額外自訂的情境。

小提醒:在同一專案中同時使用 ts-node-devnodemon 會產生衝突,請依需求挑選其一。

2. 建立 TypeScript 設定檔(tsconfig.json)

npx tsc --init

產生的 tsconfig.json 預設相當寬鬆,我們可以根據 Express 專案做以下調整:

{
  "compilerOptions": {
    "target": "ES2022",               // 使用最新的 ECMAScript 語法
    "module": "commonjs",             // Node 預設的模組系統
    "outDir": "./dist",               // 編譯後的輸出目錄
    "rootDir": "./src",               // 原始碼根目錄
    "strict": true,                   // 開啟所有嚴格型別檢查
    "esModuleInterop": true,          // 允許 import = require() 兼容
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}
  • strict 能幫助捕捉潛在的型別錯誤,對新手尤為重要。
  • outDir / rootDir 讓編譯結果與原始碼分離,保持專案結構清晰。

3. 撰寫第一個 Express 伺服器(src/index.ts)

// src/index.ts
import express, { Request, Response } from 'express';

const app = express();
const PORT = process.env.PORT || 3000;

// 中介軟體:解析 JSON
app.use(express.json());

// 範例路由
app.get('/', (req: Request, res: Response) => {
  res.send('Hello, Express + TypeScript!');
});

// 啟動伺服器
app.listen(PORT, () => {
  console.log(`🚀 Server is running at http://localhost:${PORT}`);
});

重點:使用 import 語法與型別 (Request, Response) 能讓 IDE 提供即時提示,減少拼字錯誤與參數遺漏。

4. npm script:分別使用 ts-node、ts-node-dev、nodemon

package.json 中加入以下 scripts

{
  "scripts": {
    "start": "node ./dist/index.js",                     // 已編譯的檔案
    "build": "tsc",                                      // 手動編譯
    "dev": "ts-node-dev --respawn --transpileOnly src/index.ts",
    "dev:nodemon": "nodemon --watch src -e ts --exec \"ts-node\" src/index.ts"
  }
}
  • npm run dev:利用 ts-node-dev,只在變更時重新編譯,速度快且支援熱重載。
  • npm run dev:nodemon:使用 nodemon + ts-node 的組合,適合想自行控制 nodemon 觸發條件的情況。
  • npm run build && npm start:正式上線前的編譯與執行流程。

--transpileOnlyts-node-dev 僅做語法轉譯,不執行完整型別檢查,提升開發時的回應速度;若想在開發階段就捕捉型別錯誤,可另外跑 npm run lint(結合 eslint + @typescript-eslint)。

5. 其他實用範例

5.1 使用環境變數(dotenv)

npm install -D dotenv
npm install -D @types/dotenv
// src/index.ts (加入前置載入)
import 'dotenv/config'; // 直接載入 .env 檔案

const PORT = process.env.PORT ?? 3000;

5.2 建立路由模組化

// src/routes/user.ts
import { Router, Request, Response } from 'express';

const router = Router();

router.get('/', (req: Request, res: Response) => {
  res.json([{ id: 1, name: 'Alice' }]);
});

export default router;
// src/index.ts (掛載路由)
import userRouter from './routes/user';
app.use('/api/users', userRouter);

5.3 錯誤處理中介軟體

// src/middleware/errorHandler.ts
import { Request, Response, NextFunction } from 'express';

export function errorHandler(
  err: Error,
  _req: Request,
  res: Response,
  _next: NextFunction
) {
  console.error(err);
  res.status(500).json({ message: err.message });
}
// src/index.ts
import { errorHandler } from './middleware/errorHandler';
app.use(errorHandler);

常見陷阱與最佳實踐

常見問題 為什麼會發生 解決方案 / 最佳實踐
執行 npm run dev 時,程式仍然卡在舊的 TypeScript 編譯結果 ts-node-dev 預設快取舊檔案,或 tsconfig.json 中的 outDirrootDir 設定不一致 確保 --transpileOnly--respawn 同時使用,或在 tsconfig.json 加入 "incremental": true
nodemon 無法偵測 .ts 檔案變化 預設只監看 .js,需要自行設定擴展名 nodemon.json 或指令中加入 -e ts,tsx,或使用 nodemon --watch src -e ts --exec "ts-node"
型別錯誤在開發時不被捕捉 使用 ts-node-dev 時跳過完整型別檢查 另開一個 npm run lint 任務,或在 dev script 中加入 --compilerOptions {"noEmit":false} 以強制檢查。
importrequire 混用導致 esModuleInterop 錯誤 tsconfig 沒開 esModuleInterop tsconfig.json 加上 "esModuleInterop": true,或統一使用 import
部署時忘記執行 npm run build 開發時直接使用 ts-node,產生「未編譯」的程式碼 在 CI/CD pipeline 中加入 npm ci && npm run build && npm start,確保正式環境只執行編譯後的 JavaScript。

最佳實踐

  1. 保持 scripts 簡潔:只保留 devbuildstart 三個主要指令,避免過多自訂腳本造成混亂。
  2. 使用 strict 模式:即使開發初期會多出一些型別錯誤,長遠來說能大幅降低執行時 bug。
  3. 分離開發與生產環境:開發時使用 ts-node-dev,生產時一定要走 npm run buildnpm start 的流程。
  4. 將環境變數寫入 .env,並在 .gitignore 中排除,避免機密資訊外洩。

實際應用場景

場景 為什麼選擇此組合 範例指令
快速原型(PoC) 只需要即時執行且不想每次手動編譯 npm run dev(ts-node-dev)
多人協作的微服務 需要嚴格型別檢查、CI 建置與熱重載 npm run lint && npm run dev(配合 ESLint)
Docker 部署 只跑編譯後的程式,減少映像檔大小 Dockerfile → RUN npm ci && npm run build && CMD ["node","dist/index.js"]
Serverless(AWS Lambda) 需要單檔輸出且型別安全 npm run build && npx serverless deploy(使用 tsc 產出)
本地測試環境 想要自訂監看目錄、排除測試檔 nodemon --watch src --ignore **/*.spec.ts -e ts --exec "ts-node"

總結

本篇從 npm 初始化TypeScript 設定ts-node / ts-node-dev / nodemon 四大工具的角色與使用方式,逐步建構出一套 Express + TypeScript 的開發基礎。透過 npm scripts 的規劃,我們能在開發階段享受 即時熱重載,在生產環境則以 編譯後的 JavaScript 保障執行效能與安全性。

關鍵要點

  • npm 是套件與腳本的核心,務必在 package.json 中明確定義 devbuildstart
  • ts-node-dev 提供最快的開發迭代體驗,適合日常開發;nodemon 則適合需要自訂監看規則的特殊情況。
  • 嚴格的 TypeScript 設定strictesModuleInterop)是避免未來維護痛點的根本。

完成這些步驟後,你已經具備了 可擴充、型別安全且開發友好的 ExpressJS + TypeScript 專案框架,接下來可以自由加入路由、資料庫、驗證等功能,持續打造高品質的 Node.js 應用。祝開發順利 🚀!