本文 AI 產出,尚未審核

ExpressJS (TypeScript) – 專案結構設計

主題:環境變數管理(dotenv)


簡介

Node.jsExpress 專案中,環境變數是連接資料庫、設定 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.DBHOSTDB_HOST 不同,導致 undefined。 使用 IDE 的自動補完或在 env.d.ts 中定義 ProcessEnv 介面,編譯時即能捕捉錯誤。
在生產環境仍載入 .env 生產環境通常使用容器或平台注入環境變數,.env 可能不被更新。 env.ts 中加入判斷:若 process.env.NODE_ENV === 'production',可跳過 dotenv.config(),或只載入 process.env
未對敏感資訊加密或限制權限 即使 .env 不上傳,伺服器備份或磁碟洩漏仍有風險。 使用 HashiCorp VaultAWS Parameter Store 等外部密鑰管理服務,或在 CI/CD 中使用加密變數。

最佳實踐清單

  1. 統一入口:所有環境變數皆從 config.ts 匯出,避免散落在各個檔案。
  2. 型別安全:在 env.d.ts 中宣告 ProcessEnv,讓 TypeScript 編譯時檢查。
  3. 多環境檔案:使用 .env.development.env.test.env.production,配合 NODE_ENV 自動切換。
  4. CI/CD 整合:在 GitHub Actions、GitLab CI、GitHub Codespaces 等平台,直接注入環境變數,避免把 .env 放入映像檔。
  5. 安全審計:定期檢查 .gitignore、檔案權限,確保機密資訊不會被意外曝光。

實際應用場景

1. 多資料庫連線

在大型系統中,開發、測試、正式環境會使用不同的資料庫叢集。只要在 .env 裡切換 DB_HOSTDB_PORTDB_USERDB_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 -edocker-compose.ymlenvironment 區塊注入變數。

# 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 專案將在 設定管理 方面更具彈性與安全性,為後續功能開發與部署奠定堅實基礎。祝開發順利! 🚀