本文 AI 產出,尚未審核

TypeScript 與 Node.js:ts-node 與 nodemon 的實務應用


簡介

在開發 Node.js + TypeScript 的專案時,我們常常會遇到兩個痛點:

  1. 每次修改程式碼後,都要重新編譯再啟動,浪費時間。
  2. 編譯產生的 JavaScript 檔案散落在 dist/ 目錄,讓除錯變得不直觀。

ts-nodenodemon 正是為了解決這兩個問題而誕生的工具。

  • ts-node 讓我們可以直接在執行時 即時編譯 TypeScript,不必先產出 .js 檔。
  • nodemon 則會 監看檔案變化,自動重新啟動應用程式,讓開發流程更順暢。

結合兩者,我們能在 開發階段只需要一次指令,就完成編譯、執行、熱重載的完整流程,極大提升開發效率。


核心概念

1. 為什麼需要 ts-node?

ts-node 本質上是一個 TypeScript 執行環境,它會在 Node.js 內部呼叫 TypeScript 編譯器 (tsc) 進行即時轉譯。
主要優點:

優點 說明
免編譯 直接執行 .ts 檔,省去 tsc && node 的兩步驟
即時錯誤回報 編譯錯誤會即時拋出,方便除錯
支援 tsconfig 會自動讀取專案根目錄的 tsconfig.json,保持設定一致性

安裝與基本使用

npm install -D ts-node typescript @types/node
# 直接執行單一檔案
npx ts-node src/index.ts

:若專案已安裝 typescriptts-node 會自動使用相同版本的編譯器。

2. nodemon:自動重啟的守護程式

nodemon 會監看指定的檔案或目錄,一旦偵測到變更,就會 自動結束舊的 Node 進程並重新啟動。對於前端開發者來說,這就像是瀏覽器的 Hot Reload,只是發生在後端。

安裝與基本使用

npm install -D nodemon
# 監看 src 目錄下的所有檔案,執行 index.ts(需要配合 ts-node)
npx nodemon --watch src --exec "npx ts-node" src/index.ts

小技巧:將上述指令寫入 package.jsonscripts,團隊成員只要執行 npm run dev 即可。

3. 結合 ts-node 與 nodemon:開發最佳組合

核心概念:使用 nodemon 監看 TypeScript 原始碼,並透過 ts-node 直接執行,省去編譯步驟,同時保有熱重載功能。

範例:package.json 設定

{
  "name": "ts-node-nodemon-demo",
  "version": "1.0.0",
  "scripts": {
    "dev": "nodemon --watch src --ext ts,tsx --exec \"ts-node\" src/index.ts"
  },
  "devDependencies": {
    "nodemon": "^3.0.1",
    "ts-node": "^10.9.2",
    "typescript": "^5.3.3",
    "@types/node": "^20.5.1"
  }
}

範例程式碼 1:簡易 HTTP Server

// src/index.ts
import http from "http";

const PORT = 3000;

const server = http.createServer((req, res) => {
  res.writeHead(200, { "Content-Type": "text/plain" });
  res.end("Hello from ts-node + nodemon!");
});

server.listen(PORT, () => {
  console.log(`Server is running at http://localhost:${PORT}`);
});

執行 npm run dev 後,打開瀏覽器 http://localhost:3000,每次修改 index.ts 並保存,伺服器會自動重啟,立即看到變更。

範例程式碼 2:使用環境變數與 dotenv

// src/server.ts
import http from "http";
import dotenv from "dotenv";

dotenv.config(); // 讀取 .env 檔

const PORT = Number(process.env.PORT) || 4000;

const server = http.createServer((_req, res) => {
  res.end(`Port from .env: ${PORT}`);
});

server.listen(PORT, () => {
  console.log(`Listening on ${PORT}`);
});
# .env
PORT=5000

只要改動 .envserver.tsnodemon 都會重新啟動,且 ts-node 會即時讀取最新的環境設定。

範例程式碼 3:結合 Express 與路由

// src/app.ts
import express from "express";

const app = express();

app.get("/", (_req, res) => {
  res.send("Home page");
});

app.get("/api/users", (_req, res) => {
  res.json([{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]);
});

export default app;
// src/index.ts
import app from "./app";

const PORT = 8080;

app.listen(PORT, () => {
  console.log(`Express server listening on http://localhost:${PORT}`);
});

package.json 中的 dev 指令仍然只需要指向 src/index.tsnodemon 會遞迴監看 src 目錄所有變化,無需額外設定

範例程式碼 4:自訂 nodemon 設定檔

// nodemon.json
{
  "watch": ["src"],
  "ext": "ts",
  "ignore": ["src/**/*.spec.ts"],
  "exec": "ts-node ./src/index.ts",
  "env": {
    "NODE_ENV": "development"
  }
}
# 只要執行
npx nodemon

這樣的設定讓 團隊成員 不必記住長指令,直接 npm run dev(或 npx nodemon)即可。

4. TypeScript 編譯選項的調整

即使使用 ts-node,仍建議在 tsconfig.json 中配置以下選項,以確保程式碼在開發與正式環境都能正常運作。

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "sourceMap": true,               // 讓 stack trace 能對應回 TS 檔
    "outDir": "dist"
  },
  "include": ["src/**/*.ts"]
}
  • sourceMap:即使不產出 .jsts-node 仍會根據 sourceMap 顯示正確的錯誤行號。
  • esModuleInterop:讓 import express from "express" 這類語法在 Node.js 中無縫運作。

常見陷阱與最佳實踐

陷阱 說明 解決方式
使用 ts-node 直接執行大型專案時效能下降 ts-node 每次執行都會即時編譯,對大量檔案會有明顯的啟動延遲。 開發階段使用 ts-node,正式部署時仍建議 tsc 編譯後再執行
nodemon 重啟過於頻繁 若監看的目錄包含 node_modulesdist,會造成無限重啟。 nodemon.json 或指令中 排除 這些目錄 (--ignore node_modules)。
環境變數未同步 nodemon 重新啟動時,若 dotenv 只在程式入口載入一次,變更 .env 不會即時生效。 dotenv.config() 放在 每次程式啟動的入口,或使用 nodemon--exec 參數加上 -r dotenv/config
TypeScript 路徑別名 (path alias) 無法解析 ts-node 需要額外設定才能理解 tsconfig.json 中的 paths 安裝 tsconfig-paths,並在 nodemon 指令中加入 -r tsconfig-paths/register
未設定 sourceMap,除錯時只能看到編譯後的行號 這會讓除錯變得困難。 一定要開啟 sourceMap,即使使用 ts-node

最佳實踐

  1. 專案結構

    project/
    ├─ src/
    │   ├─ index.ts
    │   └─ ... (其他模組)
    ├─ .env
    ├─ tsconfig.json
    ├─ nodemon.json
    └─ package.json
    
  2. 腳本統一管理

    "scripts": {
      "dev": "nodemon",
      "build": "tsc",
      "start": "node dist/index.js"
    }
    
  3. CI/CD 中仍使用 tsc

    • 開發階段 npm run dev(ts-node + nodemon)
    • 部署前 npm run build && npm run start(編譯後執行),確保執行效能與穩定性。
  4. 使用 --inspect 進行遠端除錯

    npm run dev -- --inspect
    

    nodemon 會將 --inspect 參數傳遞給 ts-node,讓 VS Code 等 IDE 可以直接 attach。


實際應用場景

場景 為何選擇 ts-node + nodemon
快速原型開發 不需要事先編譯,直接撰寫 .ts,即時看到結果。
API 伺服器開發 需要頻繁調整路由或驗證邏輯,熱重載可省去手動重啟的時間。
CLI 工具開發 透過 ts-node 執行指令腳本,搭配 nodemon 測試參數變化。
微服務本地測試 每個服務都有自己的 tsconfigts-node 能自動套用,nodemon 保持服務持續運行。
教學或工作坊 只要一條指令即可啟動環境,降低學員的環境設定門檻。

實務小提醒:在正式上線前,務必 關閉 ts-node,改為使用編譯後的 JavaScript,避免因即時編譯而產生的效能瓶頸與不必要的依賴。


總結

  • ts-node 讓 TypeScript 能在 Node.js 中即時編譯執行,省去繁瑣的 tsc 步驟。
  • nodemon 監看檔案變化,自動重啟應用程式,提供類似前端的 Hot Reload 體驗。
  • 兩者結合,只要一條指令(如 npm run dev),即可完成即時編譯、熱重載與環境變數的自動載入,極大提升開發效率。
  • 在開發階段使用此組合,正式部署仍建議編譯後執行,以確保效能與穩定性。
  • 注意排除不必要的監看目錄、開啟 sourceMap、正確設定 tsconfig,即可避免常見陷阱。

掌握了 ts-node + nodemon 的使用方式,你就能在 Node.js + TypeScript 的專案中,以更快的迭代速度、較低的錯誤率完成開發,為日後的上線與維運奠定堅實基礎。祝開發順利 🚀!