本文 AI 產出,尚未審核

ExpressJS (TypeScript) – 完整部署流程實戰教學


簡介

在完成 ExpressJS + TypeScript 的 API 開發後,最關鍵的步驟往往是 部署。即使程式碼寫得再好,若部署不當也會導致效能下降、資安漏洞或維運困難。這篇文章將以 完整專案 為例,從本機測試、容器化、CI/CD 到雲端服務的上線,逐步說明每個環節的要點與操作方式。

  • 為什麼要掌握完整部署流程?
    1. 可靠性:自動化部署可以避免手動錯誤,確保每次上線的環境一致。
    2. 可擴展性:透過容器與雲端平台,服務可以依需求水平擴充。
    3. 安全性:在部署階段加入環境變數、TLS、日誌管理等措施,可降低資安風險。

本教學適合 初學者到中階開發者,即使你只會寫簡單的路由,也能跟著步驟把專案推上線。


核心概念

1. 專案結構與編譯設定

使用 TypeScript 開發 Express 時,建議將 原始碼編譯結果設定檔 分開管理,方便 Docker 建置與 CI/CD。下面是一個常見的目錄範例:

my-project/
│
├─ src/                # TypeScript 原始碼
│   ├─ controllers/
│   ├─ middlewares/
│   ├─ routes/
│   └─ app.ts
│
├─ dist/               # 編譯後的 JavaScript(gitignore)
│
├─ config/
│   └─ default.json    # 設定檔 (dotenv 也可)
│
├─ .env                # 環境變數
├─ tsconfig.json
├─ package.json
└─ Dockerfile

tsconfig.json 重點

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "sourceMap": true
  },
  "exclude": ["node_modules", "dist"]
}
  • outDir 指定編譯後的輸出目錄,Docker 只需要把 dist 複製進容器。
  • sourceMap 讓錯誤回報對應到原始的 .ts,方便除錯。

2. Docker 化 – 建立可攜帶的執行環境

Docker 能讓開發、測試、上線使用 相同的執行環境,減少「在我機器上可以」的問題。以下是一個簡潔的 Dockerfile(使用 multi‑stage build):

# ---------- Build Stage ----------
FROM node:20-alpine AS builder

# 設定工作目錄
WORKDIR /app

# 只安裝 production 依賴(devDep 會在下一階段安裝)
COPY package*.json ./
RUN npm ci

# 複製 TypeScript 原始碼
COPY tsconfig.json ./
COPY src ./src

# 編譯
RUN npm run build   # => tsc

# ---------- Runtime Stage ----------
FROM node:20-alpine AS runtime

WORKDIR /app

# 只拷貝編譯好的檔案與 production 依賴
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --production

# 複製環境變數範本(實際值在執行時注入)
COPY .env.example .env

# 暴露 Port(預設 3000)
EXPOSE 3000

# 使用非 root 使用者
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

# 啟動指令
CMD ["node", "dist/app.js"]

說明

  1. Build Stage:安裝完整依賴、編譯 TypeScript,產出 dist
  2. Runtime Stage:只帶入編譯後的檔案與 production 依賴,映像檔體積更小。
  3. 非 root 使用者:提升容器安全性。

建置與測試容器

# 建置映像
docker build -t my-express-api:latest .

# 本機測試(映射 3000 port)
docker run -p 3000:3000 --env-file .env my-express-api:latest

3. CI/CD 流程 – GitHub Actions 範例

自動化測試、建置、部署是 持續交付 的核心。以下示範一個最小化的 GitHub Actions 工作流程,涵蓋 單元測試 → Docker Build → 推送至 Docker Hub → 部署至 Render(同理可換成 AWS、GCP、Azure)。

name: Deploy Express API

on:
  push:
    branches: [ main ]

jobs:
  build-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      # 安裝 Node
      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: '20'

      # 安裝相依套件
      - name: Install dependencies
        run: npm ci

      # 執行測試
      - name: Run tests
        run: npm test

      # 編譯 TypeScript
      - name: Build
        run: npm run build

  docker:
    needs: build-test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      # 登入 Docker Hub
      - name: Docker login
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      # 建置 & 標記映像
      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: ${{ secrets.DOCKER_USERNAME }}/my-express-api:latest

  deploy:
    needs: docker
    runs-on: ubuntu-latest
    steps:
      - name: Trigger Render Deploy
        run: |
          curl -X POST "https://api.render.com/v1/services/${{ secrets.RENDER_SERVICE_ID }}/deploys" \
          -H "Authorization: Bearer ${{ secrets.RENDER_API_KEY }}"

關鍵點

  • 使用 GitHub Secrets 管理 Docker Hub、Render(或其他雲端)的認證,避免憑證洩漏。
  • needs 確保 測試成功 才會進行後續的建置與部署。

4. 雲端服務(以 Render 為例)

Render 提供 自動部署HTTPS環境變數管理,對小型專案相當友善。部署步驟概述:

  1. 建立服務:選擇 Web Service → Docker → 輸入映像名稱 docker.io/<username>/my-express-api:latest
  2. 設定環境變數:在 Render 的 Environment 頁面加入 .env 中的鍵值(如 DATABASE_URLJWT_SECRET)。
  3. 啟用自動部署:勾選 Auto Deploy,每次 Docker Hub 有新標籤時會自動觸發。
  4. 自訂健康檢查:設定 /health 路徑,Render 會定期呼叫確認服務是否正常。

健康檢查路由範例(src/routes/health.ts

import { Router, Request, Response } from 'express';

const router = Router();

router.get('/health', (_req: Request, res: Response) => {
  // 只要程式可以跑到這裡,就算服務正常
  res.status(200).json({ status: 'ok', timestamp: Date.now() });
});

export default router;

app.ts 中掛載:

import healthRouter from './routes/health';
app.use('/', healthRouter);

5. 日誌與監控

在正式環境中,日誌 是排錯與監控的核心。建議搭配以下工具:

工具 用途 實作方式
Winston 結構化日誌(JSON) src/logger.ts 中設定傳送至檔案或 CloudWatch
PM2 (在非容器環境) 進程管理、崩潰自動重啟 pm2 start dist/app.js --name api
Prometheus + Grafana 監控指標(請求數、延遲) 使用 express-prom-bundle 中介軟體收集指標

Winston 基本設定(src/logger.ts

import { createLogger, format, transports } from 'winston';

const logger = createLogger({
  level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
  format: format.combine(
    format.timestamp(),
    format.errors({ stack: true }),
    format.json()
  ),
  transports: [
    new transports.Console(),
    // 只在 production 寫入檔案
    ...(process.env.NODE_ENV === 'production'
      ? [new transports.File({ filename: 'logs/combined.log' })]
      : []),
  ],
});

export default logger;

在任何路由或中介軟體中使用:

import logger from '../logger';
logger.info('User login succeeded', { userId: req.body.id });

常見陷阱與最佳實踐

陷阱 說明 最佳實踐
直接在容器內使用 npm install 會把開發依賴一起打包,映像檔過大。 使用 multi‑stage build,只保留 --production 依賴。
把機密資訊硬寫在程式碼 會在 Git、Docker 映像中洩漏。 使用 環境變數秘密管理服務(AWS Secrets Manager、GCP Secret Manager)。
未設定健康檢查 部署平台無法自動偵測失敗容器,導致服務卡住。 為 Express 加入 /health 路由,並在平台設定相應的檢查。
忽略日誌輪替 長時間運行會產生日誌檔過大,佔滿磁碟。 使用 logrotate 或將日誌直接輸出至外部服務(ELK、Datadog)。
容器內部直接暴露 0.0.0.0:3000 若未設定 EXPOSE 或平台限制,可能無法正確映射端口。 確保 app.listen(port, '0.0.0.0') 並在 Dockerfile EXPOSE 正確端口。

實際應用場景

  1. 小型 SaaS MVP

    • 使用 GitHub Actions + Render,每次 push 到 main 即自動部署。
    • 透過 Winston + CloudWatch 追蹤使用者行為與錯誤。
  2. 企業內部微服務

    • Docker ComposeKubernetes 為基礎,將 Express 服務作為 REST API 與其他服務(例如 RabbitMQ、PostgreSQL)協作。
    • 使用 Helm Chart 管理多環境(dev、staging、prod)設定。
  3. IoT 後端平台

    • 需要 高併發低延遲,可在 Docker 中加入 Node.js Clustercluster.fork())或 PM2 以多核利用。
    • 搭配 NGINX 反向代理與 Let's Encrypt 自動簽發 TLS,確保資料傳輸安全。

總結

部署一個 ExpressJS + TypeScript 的完整 API 服務,核心在於 環境一致性自動化流程安全與可觀測性。本文從專案結構、Docker 多階段建置、CI/CD、雲端平台配置,到日誌與監控,提供了完整的實作藍圖。只要依照以下步驟:

  1. 規劃目錄與 TypeScript 設定
  2. 撰寫健康檢查與結構化日誌
  3. 以 multi‑stage Dockerfile 打包
  4. 使用 GitHub Actions 完成測試、建置、推送
  5. 在 Render(或其他雲端)設定環境變數與自動部署

就能把本機開發的 API,安全、快速且可擴充地上線到正式環境。未來若有更高階需求(K8s、服務網格、藍綠部署),只要在此基礎上加入相應的工具與流程,即可無縫擴展。祝你部署順利,服務穩定!