全端開發技能 (UI 與後端)
此技能提供了一個端到端的架構指南、工作流程手冊以及編碼標準,用於在現代 TypeScript/JavaScript 環境中開發達到生產等級的網頁應用程式,同時涵蓋前端 (UI) 與後端。
1. 核心架構原則
- 端到端型別安全:
- 為領域模型和資料傳輸物件 (DTO) 建立單一事實來源。
- 使用結構驗證函式庫 (例如 Zod) 在執行階段解析和驗證請求,並在建置階段推斷 TypeScript 型別。
- 分層關注點分離:
- 前端:嚴格分離展示性原始元件、業務/容器元件以及資料擷取鉤子。
- 後端:嚴格分離路由/控制器 (HTTP 傳輸層)、服務層 (商業邏輯) 以及資料存取 / Repository 層 (資料庫 / ORM)。
- API 合約優先:
- 在實作 UI 或後端細節之前,先建立請求和回應合約。
- 統一回應封裝:
{ success: true, data: T }或{ success: false, error: { message, code, details } }。
- 韌性與錯誤處理:
- 前端:錯誤邊界、骨架載入狀態、優雅的備援方案以及使用者友善的通知。
- 後端:集中式錯誤中介軟體、操作性錯誤 vs 程式設計錯誤分類、結構化日誌以及故障安全預設值。
- 預設安全性:
- 安全性標頭 (Helmet)、明確的 CORS 政策、輸入驗證/清理、速率限制以及安全的驗證機制 (HTTP-only Cookie 或 Bearer Token)。
2. 建議的目錄結構
適用於全端儲存庫 (Monorepo 或分離結構):
├── packages/ or apps/
│ ├── client/ # 前端 UI (React / Next.js / Vite)
│ │ ├── src/
│ │ │ ├── assets/ # 靜態資源 (圖片、圖示、字型)
│ │ │ ├── components/ # UI 元件 (原子、分子、有機體)
│ │ │ │ ├── ui/ # 核心設計系統原始元件 (Button、Input、Modal)
│ │ │ │ └── features/ # 領域特定的複合元件
│ │ │ ├── hooks/ # 自訂 React Hook (useAuth、useDebounce 等)
│ │ │ ├── services/ # API 用戶端與服務整合
│ │ │ ├── store/ # 全域狀態管理 (Zustand、Context 等)
│ │ │ ├── types/ # 用戶端專屬型別
│ │ │ ├── utils/ # 輔助函式與格式化工具
│ │ │ ├── App.tsx # 主要應用程式元件與路由
│ │ │ ├── main.tsx # 進入點
│ │ │ └── index.css # 全域 CSS 與設計符號
│ │ └── package.json
│ │
│ ├── server/ # 後端 API (Node.js / Express / Fastify)
│ │ ├── src/
│ │ │ ├── config/ # 環境變數與執行時期設定
│ │ │ ├── controllers/ # HTTP 路由處理器 (提取參數、呼叫服務)
│ │ │ ├── middleware/ # 驗證、驗證、錯誤處理、速率限制
│ │ │ ├── models/ # 資料庫模型 / Schema (Prisma、Drizzle 等)
│ │ │ ├── routes/ # Express/Fastify 路由定義
│ │ │ ├── services/ # 商業邏輯與外部 API 互動
│ │ │ ├── utils/ # 後端輔助工具 (jwt、logger、crypto)
│ │ │ ├── app.ts # Express/Fastify 應用程式設定
│ │ │ └── server.ts # 伺服器進入點與優雅關閉
│ │ └── package.json
│ │
│ └── shared/ # 共享 Schema、DTO 與 TypeScript 合約
│ ├── src/
│ │ ├── schemas/ # Zod Schema (user.schema.ts、auth.schema.ts)
│ │ └── types/ # 推斷型別 (export type User = z.infer<...>)
│ └── package.json
3. 逐步開發工作流程
在實作任何全端功能時,請依序執行以下階段:
階段 1:定義共享合約與 Schema
- 在
shared/或common/中定義代表請求負載和回應實體的 Zod Schema。 - 匯出推斷出的 TypeScript 型別。
import { z } from 'zod';
export const CreateUserSchema = z.object({
email: z.string().email('無效的電子郵件地址'),
name: z.string().min(2, '名稱至少需要 2 個字元'),
role: z.enum(['admin', 'user']).default('user'),
});
export type CreateUserInput = z.infer<typeof CreateUserSchema>;
export type UserResponse = CreateUserInput & { id: string; createdAt: string };
階段 2:建置後端服務與端點
- 驗證中介軟體:使用 Zod Schema 套用驗證中介軟體,以便在早期拒絕對格式不正確的輸入並回傳 HTTP 400。
- 服務層:在服務類別或模組中編寫純商業邏輯。將資料庫查詢與 HTTP 概念 (
req、res) 隔離開來。 - 控制器:處理 HTTP 輸入提取、呼叫服務,並使用
sendSuccess()回傳標準化回應。 - 錯誤處理:使用自訂錯誤 (
NotFoundError、BadRequestError、UnauthorizedError),並讓集中式錯誤中介軟體捕捉錯誤並格式化回應。
階段 3:建置前端 UI 元件
- 設計系統符號:確保存在用於顏色、排版、間距和陰影的 CSS 自訂屬性或設計系統變數。
- 原子 / UI 原始元件:建立或重複使用基礎 UI 元件 (按鈕、輸入欄位、徽章、對話框),並具備鍵盤導航和 ARIA 屬性。
- 功能視圖 / 頁面:組合具有清晰視覺層次、寬敞留白、現代排版和響應式網格/flexbox 佈局的複合元件。
- 狀態與微互動:
- 提供懸停、啟動、焦點可見和禁用狀態的視覺回饋。
- 在待處理提交期間包含骨架載入指示器、旋轉圖示和禁用按鈕。
階段 4:整合與資料擷取
- API 用戶端:使用配置了基礎 URL、逾時、憑證和攔截器的集中式 fetch 包裝函式或 Axios 實例。
- 伺服器狀態:使用 TanStack Query (React Query) 或標準 Hook 來查詢和變更資料。
- 在成功變更時使快取失效。
- 在適當情況下使用樂觀更新以獲得流暢的使用者體驗。
- 表單處理:使用 React Hook Form + Zod resolver 整合表單,以便在客戶端和伺服器之間共享完全相同的驗證規則。
階段 5:驗證與強化
- 後端驗證:使用單元/整合測試或 curl/fetch 測試端點,以驗證邊界情況 (無效輸入、缺少 token、重複鍵)。
- 前端驗证:驗證在桌面、平板和手機視口寬度下的響應式佈局。
- 安全性檢查:確認 CORS、速率限制和驗證防護已啟用。
4. 快速程式碼模式
標準 API 回應輔助程式 (後端)
// server/src/utils/response.ts
import { Response } from 'express';
export function sendSuccess<T>(res: Response, data: T, statusCode = 200, meta?: unknown) {
return res.status(statusCode).json({
success: true,
data,
...(meta ? { meta } : {}),
});
}
export function sendError(res: Response, message: string, statusCode = 500, details?: unknown) {
return res.status(statusCode).json({
success: false,
error: {
message,
statusCode,
details: details ?? null,
},
});
}
集中式 Express 錯誤中介軟體
// server/src/middleware/errorHandler.ts
import { Request, Response, NextFunction } from 'express';
import { ZodError } from 'zod';
export class AppError extends Error {
constructor(public message: string, public statusCode = 500, public details?: unknown) {
super(message);
this.name = 'AppError';
}
}
export function errorHandler(err: unknown, req: Request, res: Response, next: NextFunction) {
if (err instanceof ZodError) {
return res.status(400).json({
success: false,
error: {
message: '驗證失敗',
statusCode: 400,
details: err.flatten().fieldErrors,
},
});
}
if (err instanceof AppError) {
return res.status(err.statusCode).json({
success: false,
error: {
message: err.message,
statusCode: err.statusCode,
details: err.details ?? null,
},
});
}
console.error('未處理的伺服器錯誤:', err);
return res.status(500).json({
success: false,
error: {
message: '內部伺服器錯誤',
statusCode: 500,
},
});
}
型別安全 API 用戶端 (前端)
// client/src/services/apiClient.ts
interface ApiResponse<T> {
success: boolean;
data: T;
error?: {
message: string;
statusCode: number;
details?: unknown;
};
}
export async function apiRequest<T>(url: string, options?: RequestInit): Promise<T> {
const res = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
...options,
});
const body: ApiResponse<T> = await res.json();
if (!res.ok || !body.success) {
throw new Error(body.error?.message || `HTTP ${res.status}: 請求失敗`);
}
return body.data;
}
5. 參考資料與深入研究
如需深入的架構模式,請參閱: