ExpressJS (TypeScript) – 完整部署流程實戰教學
簡介
在完成 ExpressJS + TypeScript 的 API 開發後,最關鍵的步驟往往是 部署。即使程式碼寫得再好,若部署不當也會導致效能下降、資安漏洞或維運困難。這篇文章將以 完整專案 為例,從本機測試、容器化、CI/CD 到雲端服務的上線,逐步說明每個環節的要點與操作方式。
- 為什麼要掌握完整部署流程?
- 可靠性:自動化部署可以避免手動錯誤,確保每次上線的環境一致。
- 可擴展性:透過容器與雲端平台,服務可以依需求水平擴充。
- 安全性:在部署階段加入環境變數、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"]
說明
- Build Stage:安裝完整依賴、編譯 TypeScript,產出
dist。 - Runtime Stage:只帶入編譯後的檔案與 production 依賴,映像檔體積更小。
- 非 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、環境變數管理,對小型專案相當友善。部署步驟概述:
- 建立服務:選擇 Web Service → Docker → 輸入映像名稱
docker.io/<username>/my-express-api:latest。 - 設定環境變數:在 Render 的 Environment 頁面加入
.env中的鍵值(如DATABASE_URL、JWT_SECRET)。 - 啟用自動部署:勾選 Auto Deploy,每次 Docker Hub 有新標籤時會自動觸發。
- 自訂健康檢查:設定
/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 正確端口。 |
實際應用場景
小型 SaaS MVP
- 使用 GitHub Actions + Render,每次 push 到
main即自動部署。 - 透過 Winston + CloudWatch 追蹤使用者行為與錯誤。
- 使用 GitHub Actions + Render,每次 push 到
企業內部微服務
- 以 Docker Compose 或 Kubernetes 為基礎,將 Express 服務作為 REST API 與其他服務(例如 RabbitMQ、PostgreSQL)協作。
- 使用 Helm Chart 管理多環境(dev、staging、prod)設定。
IoT 後端平台
- 需要 高併發 與 低延遲,可在 Docker 中加入 Node.js Cluster(
cluster.fork())或 PM2 以多核利用。 - 搭配 NGINX 反向代理與 Let's Encrypt 自動簽發 TLS,確保資料傳輸安全。
- 需要 高併發 與 低延遲,可在 Docker 中加入 Node.js Cluster(
總結
部署一個 ExpressJS + TypeScript 的完整 API 服務,核心在於 環境一致性、自動化流程 與 安全與可觀測性。本文從專案結構、Docker 多階段建置、CI/CD、雲端平台配置,到日誌與監控,提供了完整的實作藍圖。只要依照以下步驟:
- 規劃目錄與 TypeScript 設定
- 撰寫健康檢查與結構化日誌
- 以 multi‑stage Dockerfile 打包
- 使用 GitHub Actions 完成測試、建置、推送
- 在 Render(或其他雲端)設定環境變數與自動部署
就能把本機開發的 API,安全、快速且可擴充地上線到正式環境。未來若有更高階需求(K8s、服務網格、藍綠部署),只要在此基礎上加入相應的工具與流程,即可無縫擴展。祝你部署順利,服務穩定!