ExpressJS (TypeScript) – 專案結構設計
主題:環境變數管理(dotenv)
簡介
在 Node.js 與 Express 專案中,環境變數是連接資料庫、設定 API 金鑰、切換測試與正式環境的關鍵。若把所有設定硬寫在程式碼裡,不僅不安全,也會讓部署變得繁雜。
使用 dotenv 套件搭配 TypeScript,可以把所有機密資訊統一放在 .env 檔案中,讓程式碼保持乾淨、可測試,同時避免敏感資料意外洩漏到版本控制系統。
本篇文章將說明 dotenv 的運作原理、在 Express + TypeScript 專案中的最佳實踐,並提供多個可直接套用的程式碼範例,協助你在開發與部署階段快速、安心地管理環境變數。
核心概念
1. 為什麼要使用 .env 檔案?
- 分離設定與程式碼:程式碼只負責邏輯,設定則交給外部檔案管理。
- 多環境切換:開發、測試、正式環境只需切換不同的
.env檔案或在 CI/CD 中注入變數。 - 安全性:將金鑰、密碼等機密資訊排除於 Git,降低泄漏風險。
小技巧:在
.gitignore中加入.env*,確保所有.env檔案不會被提交。
2. 安裝與基本設定
# npm
npm i dotenv
npm i -D @types/node # TypeScript 需要 Node 的型別定義
# yarn
yarn add dotenv
yarn add -D @types/node
在 專案根目錄 建立 .env,範例:
# .env
PORT=3000
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASS=SuperSecretPassword
JWT_SECRET=MyJwtSecretKey
注意:
.env檔案的每一行皆為KEY=VALUE,值中若有空白需使用雙引號包住,例如API_URL="https://api.example.com/v1"。
3. 在 TypeScript 中載入環境變數
3.1 建立 src/config/env.ts
// src/config/env.ts
import * as dotenv from 'dotenv';
import * as path from 'path';
// 依據 NODE_ENV 載入不同的 .env 檔案
const envFile = `.env${process.env.NODE_ENV ? `.${process.env.NODE_ENV}` : ''}`;
dotenv.config({ path: path.resolve(process.cwd(), envFile) });
/**
* 將環境變數轉型別,避免在程式中直接使用 string
*/
export const config = {
port: Number(process.env.PORT) || 3000,
db: {
host: process.env.DB_HOST ?? 'localhost',
port: Number(process.env.DB_PORT) ?? 5432,
user: process.env.DB_USER ?? '',
password: process.env.DB_PASS ?? '',
},
jwtSecret: process.env.JWT_SECRET ?? '',
};
說明
dotenv.config()會把.env內容寫入process.env。- 使用
process.env.NODE_ENV可自動切換.env.development、.env.production等檔案。 - 透過
config物件把字串型別轉成正確的型別(如Number),讓 TypeScript 在編譯時就能檢查。
3.2 在 src/app.ts 中使用
// src/app.ts
import express from 'express';
import { config } from './config/env';
const app = express();
// 中間件、路由設定略...
app.listen(config.port, () => {
console.log(`🚀 Server is running on http://localhost:${config.port}`);
});
4. 讓 TypeScript 知道環境變數的型別
建立 型別宣告檔 src/types/env.d.ts:
// src/types/env.d.ts
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV?: 'development' | 'production' | 'test';
PORT?: string;
DB_HOST?: string;
DB_PORT?: string;
DB_USER?: string;
DB_PASS?: string;
JWT_SECRET?: string;
}
}
在 tsconfig.json 中加入 typeRoots 或直接把檔案放在 src/@types 目錄,讓編譯器自動載入。
5. 多環境範例
5.1 建立 .env.development、.env.test、.env.production
# .env.development
PORT=3000
DB_HOST=localhost
DB_USER=dev_user
DB_PASS=dev_pass
JWT_SECRET=dev_secret
# .env.test
PORT=4000
DB_HOST=localhost
DB_USER=test_user
DB_PASS=test_pass
JWT_SECRET=test_secret
# .env.production
PORT=80
DB_HOST=db.production.example.com
DB_USER=prod_user
DB_PASS=prod_pass
JWT_SECRET=prod_secret
啟動方式:
# 開發環境
npm run dev # 內部會設定 NODE_ENV=development
# 測試環境
npm run test # 內部會設定 NODE_ENV=test
# 正式環境
npm start # 內部會設定 NODE_ENV=production
小技巧:在
package.json中的 script 可寫成"dev": "cross-env NODE_ENV=development ts-node-dev src/app.ts"
需要cross-env套件跨平台支援 Windows。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
忘記將 .env 加入 .gitignore |
機密資訊會被推送到遠端,造成安全漏洞。 | 在 .gitignore 中加入 /.env*,並在團隊文件說明必須自行建立本機 .env。 |
直接在程式碼中使用 process.env |
失去型別安全,且每次使用都要自行轉型別。 | 建議建立 config.ts,一次性轉型別並匯出統一介面。 |
| 變數名稱拼寫錯誤 | process.env.DBHOST 與 DB_HOST 不同,導致 undefined。 |
使用 IDE 的自動補完或在 env.d.ts 中定義 ProcessEnv 介面,編譯時即能捕捉錯誤。 |
在生產環境仍載入 .env |
生產環境通常使用容器或平台注入環境變數,.env 可能不被更新。 |
在 env.ts 中加入判斷:若 process.env.NODE_ENV === 'production',可跳過 dotenv.config(),或只載入 process.env。 |
| 未對敏感資訊加密或限制權限 | 即使 .env 不上傳,伺服器備份或磁碟洩漏仍有風險。 |
使用 HashiCorp Vault、AWS Parameter Store 等外部密鑰管理服務,或在 CI/CD 中使用加密變數。 |
最佳實踐清單
- 統一入口:所有環境變數皆從
config.ts匯出,避免散落在各個檔案。 - 型別安全:在
env.d.ts中宣告ProcessEnv,讓 TypeScript 編譯時檢查。 - 多環境檔案:使用
.env.development、.env.test、.env.production,配合NODE_ENV自動切換。 - CI/CD 整合:在 GitHub Actions、GitLab CI、GitHub Codespaces 等平台,直接注入環境變數,避免把
.env放入映像檔。 - 安全審計:定期檢查
.gitignore、檔案權限,確保機密資訊不會被意外曝光。
實際應用場景
1. 多資料庫連線
在大型系統中,開發、測試、正式環境會使用不同的資料庫叢集。只要在 .env 裡切換 DB_HOST、DB_PORT、DB_USER、DB_PASS,程式碼不需要任何改動。
// src/database/index.ts
import { Pool } from 'pg';
import { config } from '../config/env';
export const pgPool = new Pool({
host: config.db.host,
port: config.db.port,
user: config.db.user,
password: config.db.password,
});
2. 第三方 API 金鑰
如 Stripe、SendGrid、Firebase 等服務的金鑰,皆應放在 .env,同時在 config.ts 中提供安全的存取方式。
// src/services/payment.ts
import Stripe from 'stripe';
import { config } from '../config/env';
export const stripe = new Stripe(config.stripeSecretKey, {
apiVersion: '2023-10-16',
});
3. Docker 容器化
在 Dockerfile 中不建議直接 COPY .env,而是使用 docker run -e 或 docker-compose.yml 的 environment 區塊注入變數。
# docker-compose.yml
services:
api:
image: my-express-app
environment:
- NODE_ENV=production
- PORT=8080
- DB_HOST=db.prod.internal
- DB_USER=prod_user
- DB_PASS=${DB_PASS} # 由 host 環境變數帶入
總結
- dotenv 為 Node.js/Express 專案提供了簡潔且安全的環境變數管理方式。
- 透過 TypeScript 的型別系統,我們可以將環境變數轉成安全的
config物件,避免在程式碼中直接操作process.env。 - 多環境檔案、型別宣告與 CI/CD 整合 是提升專案可維護性與安全性的關鍵。
- 實務上,無論是資料庫連線、第三方 API 金鑰,或是容器部署,都能藉由
.env檔案統一管理,減少硬編碼、降低錯誤機率。
掌握了上述概念與實作步驟後,你的 Express + TypeScript 專案將在 設定管理 方面更具彈性與安全性,為後續功能開發與部署奠定堅實基礎。祝開發順利! 🚀