ExpressJS (TypeScript) – 基礎概念與環境設定
主題:初始化專案 ── npm、ts-node、ts-node‑dev、nodemon
簡介
在使用 ExpressJS 開發 API 或 Web 應用時,若想同時享有 TypeScript 的型別安全與 Node.js 的執行效率,最關鍵的第一步就是把開發環境建好。
npm(Node Package Manager)負責管理套件、腳本與相依性;而 ts-node、ts-node-dev、nodemon 則是讓我們在 TypeScript 原始檔上即時執行、熱重載的利器。
若環境設定不當,常會遇到「編譯慢、錯誤訊息難追蹤」或「每次修改都必須手動重啟」的痛點,直接影響開發效率與除錯體驗。本文將一步步說明如何正確初始化專案,並提供實務範例,幫助你在最短時間內建立 Express + TypeScript 的開發基礎。
核心概念
1. npm 初始化與套件安裝
# 建立一個全新的資料夾,並進入該目錄
mkdir my-express-app && cd my-express-app
# 初始化 npm,產生 package.json(-y 直接套用預設值)
npm init -y
package.json會記錄專案名稱、版本、相依套件與自訂腳本,是 Node 生態系的核心檔案。
接著安裝 Express、TypeScript 以及開發時需要的工具:
# 基本執行時相依
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-dev與nodemon會產生衝突,請依需求挑選其一。
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:正式上線前的編譯與執行流程。
註:
--transpileOnly讓ts-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 中的 outDir 與 rootDir 設定不一致 |
確保 --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} 以強制檢查。 |
import 與 require 混用導致 esModuleInterop 錯誤 |
tsconfig 沒開 esModuleInterop |
在 tsconfig.json 加上 "esModuleInterop": true,或統一使用 import。 |
部署時忘記執行 npm run build |
開發時直接使用 ts-node,產生「未編譯」的程式碼 |
在 CI/CD pipeline 中加入 npm ci && npm run build && npm start,確保正式環境只執行編譯後的 JavaScript。 |
最佳實踐
- 保持
scripts簡潔:只保留dev、build、start三個主要指令,避免過多自訂腳本造成混亂。 - 使用
strict模式:即使開發初期會多出一些型別錯誤,長遠來說能大幅降低執行時 bug。 - 分離開發與生產環境:開發時使用
ts-node-dev,生產時一定要走npm run build→npm start的流程。 - 將環境變數寫入
.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中明確定義dev、build、start。- ts-node-dev 提供最快的開發迭代體驗,適合日常開發;nodemon 則適合需要自訂監看規則的特殊情況。
- 嚴格的 TypeScript 設定(
strict、esModuleInterop)是避免未來維護痛點的根本。
完成這些步驟後,你已經具備了 可擴充、型別安全且開發友好的 ExpressJS + TypeScript 專案框架,接下來可以自由加入路由、資料庫、驗證等功能,持續打造高品質的 Node.js 應用。祝開發順利 🚀!