本文 AI 產出,尚未審核

TypeScript 與 Node.js:環境變數定義(process.env)實務應用


簡介

Node.jsTypeScript 的專案中,環境變數是管理 設定、機密資訊(例如資料庫連線字串、API 金鑰、Port)最常見且安全的方式。透過 process.env,我們可以在不同的執行環境(開發、測試、正式)間切換設定,而不必把敏感資訊寫死在程式碼裡。

對於使用 TypeScript 的開發者來說,process.env 的型別安全是一大挑戰:原生的 process.env 只是一個 NodeJS.ProcessEnv{ [key: string]: string | undefined }),若直接使用會失去 TypeScript 的靜態檢查優勢。本文將說明 如何在 TypeScript 專案中正確、有效率地使用環境變數,並提供多個實作範例、常見陷阱與最佳實踐,讓你在實務開發中既安全又便利。


核心概念

1. process.env 的基本使用

process.env 是 Node.js 提供的全域物件,代表執行程式時的環境變數集合。每個變數的值都是 字串(或 undefined),因此在 TypeScript 中直接取用時會失去類型資訊。

// 直接使用 process.env(缺乏型別安全)
const port = process.env.PORT;          // type: string | undefined
console.log(`Server listening on ${port}`);

⚠️ 注意:若環境變數未設定,port 會是 undefined,若直接傳給 listen 會導致程式在執行時拋錯。

2. 為環境變數建立型別宣告

為了讓 TypeScript 能在編譯階段檢查,我們可以自訂 環境變數的型別宣告檔env.d.ts),將常用的變數列舉出來並指定型別。

// env.d.ts
declare namespace NodeJS {
  interface ProcessEnv {
    /** 伺服器監聽埠號,預設 3000 */
    readonly PORT?: string;
    /** 資料庫連線字串 */
    readonly DATABASE_URL: string;
    /** 是否為正式環境 */
    readonly NODE_ENV: 'development' | 'production' | 'test';
    /** 第三方 API 金鑰 */
    readonly API_KEY?: string;
  }
}

重點:使用 readonly 可以避免在程式碼中意外改寫環境變數,並且透過聯合型別(如 NodeEnv)限制允許的字串集合。

3. 建立環境變數的「安全」讀取工具

即使有型別宣告,我們仍需處理 缺失或格式錯誤 的情況。以下提供一個簡潔的 config.ts,封裝環境變數的讀取與驗證。

// config.ts
import * as dotenv from 'dotenv';

// 先載入 .env 檔(如果存在)
dotenv.config();

type Env = {
  port: number;
  databaseUrl: string;
  nodeEnv: 'development' | 'production' | 'test';
  apiKey?: string;
};

/**
 * 取得環境變數,若缺少必填項目會直接拋出錯誤
 */
function getEnv(): Env {
  const {
    PORT,
    DATABASE_URL,
    NODE_ENV,
    API_KEY,
  } = process.env;

  if (!DATABASE_URL) {
    throw new Error('❌ 環境變數 DATABASE_URL 必須設定!');
  }
  if (!NODE_ENV) {
    throw new Error('❌ 環境變數 NODE_ENV 必須設定!');
  }

  const port = PORT ? Number(PORT) : 3000; // 預設 3000
  if (Number.isNaN(port) || port <= 0) {
    throw new Error('❌ PORT 必須是一個正整數!');
  }

  return {
    port,
    databaseUrl: DATABASE_URL,
    nodeEnv: NODE_ENV as Env['nodeEnv'],
    apiKey: API_KEY,
  };
}

// 直接匯出已驗證的設定,供整個專案使用
export const env = getEnv();

範例說明

  1. dotenv:在開發環境常用的套件,可將 .env 檔內容注入 process.env
  2. 必填檢查DATABASE_URLNODE_ENV 為關鍵設定,缺少時即拋錯,避免程式在執行階段才失敗。
  3. 型別轉換PORT 由字串轉為 number,同時提供預設值。

4. 在程式中使用已封裝的 env

// server.ts
import http from 'http';
import { env } from './config';

const server = http.createServer((req, res) => {
  res.end('Hello TypeScript + Node.js');
});

server.listen(env.port, () => {
  console.log(`🚀 Server running on http://localhost:${env.port} (${env.nodeEnv})`);
});

好處:所有環境變數都已在 config.ts 中驗證過,server.ts 只需要關注業務邏輯,減少重複檢查的程式碼。

5. 多環境的 .env 檔管理

檔案名稱 說明
.env 預設開發環境設定
.env.test 測試環境(例如 Jest)
.env.production 正式環境(部署時使用)

在部署腳本或 CI/CD 中,只需要設定 NODE_ENV,再由 dotenv 自動載入對應檔案:

// bootstrap.ts
import * as dotenv from 'dotenv';
import * as path from 'path';

const envFile = `.env.${process.env.NODE_ENV ?? 'development'}`;
dotenv.config({ path: path.resolve(__dirname, '..', envFile) });

6. 讓 TypeScript 編譯器認識 .env 變數

若使用 ESLintPrettier,可能會出現「process.env 可能未定義」的警告。只要在 tsconfig.json 中加入 types 設定,即可讓編譯器載入自訂的型別宣告檔:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "typeRoots": ["./node_modules/@types", "./src/types"]
  }
}

常見陷阱與最佳實踐

陷阱 說明 解決方案
直接使用 process.env 缺乏型別安全,容易在執行時因 undefined 或字串未轉型而錯誤。 建立 型別宣告 (env.d.ts) 並封裝讀取函式 (config.ts)。
環境變數未轉型 process.env.PORT 仍是字串,傳給 listen 需要 number config.ts顯式轉型Number())並檢查 NaN。
在程式碼中硬編碼環境變數名稱 若變數名稱改變,所有檔案都要手動更新。 使用 常數或介面type EnvKeys = keyof NodeJS.ProcessEnv)集中管理。
.env 檔未加入 .gitignore 機密資訊會被推到遠端儲存庫。 確認 .gitignore 包含 .env*,並在 CI 中使用 Secret 管理(GitHub Actions、GitLab CI)。
不同環境使用同一套 .env 測試環境與正式環境共用同一資料庫,導致資料污染。 建立 多個 .env.* 檔,並在部署腳本中指定正確檔案。
忘記在 Docker / PM2 等容器中設定環境變數 本地測試正常,部署後崩潰。 Dockerfiledocker-compose.ymlPM2 ecosystem file 中明確宣告 environment

最佳實踐清單

  1. 型別宣告 + 封裝env.d.ts + config.ts
  2. 使用 dotenv:只在開發或測試環境載入 .env,正式環境使用平台變數。
  3. 預設值與驗證:所有必填變數必須在啟動前驗證,缺失即拋錯。
  4. 保密資訊絕不寫入程式碼:金鑰、密碼、憑證等只能放在環境變數或 Secret 管理系統。
  5. 版本控制.env.example 放入版本庫,說明必填欄位與範例值;.env* 本身加入 .gitignore
  6. 自動化測試:在 CI 中使用 dotenv-cli 或直接在測試腳本前 export NODE_ENV=test,確保測試環境的變數正確。

實際應用場景

1. 建立可配置的 API 伺服器

// src/app.ts
import express, { Request, Response } from 'express';
import { env } from './config';

const app = express();

app.get('/status', (_req: Request, res: Response) => {
  res.json({
    env: env.nodeEnv,
    version: process.env.npm_package_version,
    uptime: process.uptime(),
  });
});

app.listen(env.port, () => {
  console.log(`📡 API Server listening on ${env.port}`);
});

使用情境

  • 開發NODE_ENV=developmentPORT=4000
  • 測試NODE_ENV=testPORT=5000(測試用)。
  • 正式NODE_ENV=productionPORT=80,同時透過 DATABASE_URL 連接雲端資料庫。

2. 與第三方服務(如 Stripe)整合

// src/payment/stripe.ts
import Stripe from 'stripe';
import { env } from '../config';

if (!env.apiKey) {
  throw new Error('❌ Stripe API key 未設定(API_KEY)');
}

export const stripe = new Stripe(env.apiKey, {
  apiVersion: '2023-10-05',
});

說明:金鑰只在環境變數中保存,避免在 Git 中洩漏;部署時可在雲端平台(Heroku、Vercel)設定 API_KEY

3. Docker + Node.js + TypeScript 的完整範例

Dockerfile

# 建立階段
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src ./src
RUN npm run build   # tsc 產出 dist

# 執行階段
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --production
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "dist/server.js"]

docker-compose.yml

version: '3.8'
services:
  api:
    build: .
    ports:
      - "3000:3000"
    env_file:
      - .env.production   # 只在正式環境使用
    restart: unless-stopped

重點:Docker 只在 env_file 中注入環境變數,程式碼本身仍透過 config.ts 取得並驗證。


總結

  • process.env 是 Node.js 提供的全域環境變數容器,但在 TypeScript 中若不加以型別宣告與驗證,會失去靜態檢查的好處。
  • 透過 自訂型別宣告 (env.d.ts)封裝讀取與驗證 (config.ts),我們可以在開發階段即捕捉缺失或格式錯誤,提升程式的可靠性。
  • 使用 dotenv 方便在本機或測試環境快速載入 .env 檔;在正式環境則建議直接使用平台提供的環境變數或 Secret 管理服務。
  • 最佳實踐 包括:型別安全、預設值與驗證、.gitignore 隱藏機密、分環境 .env 檔、Docker/CI 中明確設定。
  • 實務上,環境變數不只用於 Port、Database URL,還涵蓋 API 金鑰、第三方服務憑證、Feature Flag 等,正確的管理方式能讓專案在 開發 → 測試 → 部署 的全流程中保持一致且安全。

掌握以上概念與技巧,你就能在 Node.js + TypeScript 專案中,像使用其他 TypeScript 型別一樣安全、方便地使用環境變數,讓應用程式在不同執行環境中快速切換、易於維護。祝你開發順利!