フルスタックスキル(UI&バックエンド)
このスキルは、モダンなTypeScript/JavaScriptを使用したフロントエンド(UI)とバックエンドの両方で、本番グレードのWebアプリケーションを開発するためのエンドツーエンドのアーキテクチャガイド、ワークフロー実行手順書、およびコーディング標準を提供します。
1. コアアーキテクチャ原則
- エンドツーエンドの型安全性:
- ドメインモデルとデータ転送オブジェクト(DTO)の単一情報源。
- スキーマ検証ライブラリ(Zodなど)を使用して、リクエストを実行時に解析・検証し、コンパイル時にTypeScriptの型を推論します。
- 関心の分離(レイヤー化):
- フロントエンド: プレゼンテーションのプリミティブ、ビジネス/コンテナコンポーネント、データフェッチングフックを厳密に分離します。
- バックエンド: ルーティング/コントローラー(HTTPトランスポート)、サービスレイヤー(ビジネスロジック)、データアクセス/リポジトリレイヤー(データベース/ORM)を厳密に分離します。
- APIファーストの契約:
- UIやバックエンドの詳細を実装する前に、リクエストとレスポンスの契約を確立します。
- 統一されたレスポンスエンベロープ:
{ success: true, data: T }または{ success: false, error: { message, code, details } }。
- 回復力とエラーハンドリング:
- フロントエンド: エラーバウンダリ、スケルトンローディング状態、グレースフルフォールバック、ユーザーフレンドリーな通知。
- バックエンド: 一元化されたエラーミドルウェア、操作エラーとプログラマエラーの分類、構造化ロギング、フェイルセーフなデフォルト。
- デフォルトでのセキュリティ:
- セキュリティヘッダー(Helmet)、明示的なCORSポリシー、入力検証/サニタイゼーション、レート制限、セキュアな認証(HTTP専用クッキーまたはベアラートークン)。
2. 推奨ディレクトリ構造
フルスタックリポジトリ(モノレポまたは分割構造)の場合:
├── packages/ または apps/
│ ├── client/ # フロントエンドUI (React / Next.js / Vite)
│ │ ├── src/
│ │ │ ├── assets/ # 静的アセット (画像、アイコン、フォント)
│ │ │ ├── components/ # UIコンポーネント (原子、分子、有機体)
│ │ │ │ ├── ui/ # コアデザインシステムプリミティブ (Button, Input, Modal)
│ │ │ │ └── features/ # ドメイン固有の複合コンポーネント
│ │ │ ├── hooks/ # カスタムReactフック (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/ # データベースモデル/スキーマ (Prisma, Drizzle など)
│ │ │ ├── routes/ # Express/Fastify ルート定義
│ │ │ ├── services/ # ビジネスロジック & 外部API連携
│ │ │ ├── utils/ # バックエンドヘルパー (jwt, logger, crypto)
│ │ │ ├── app.ts # Express/Fastify アプリケーション設定
│ │ │ └── server.ts # サーバーエントリポイント & グレースフルシャットダウン
│ │ └── package.json
│ │
│ └── shared/ # 共有スキーマ、DTO、TypeScript契約
│ ├── src/
│ │ ├── schemas/ # Zodスキーマ (user.schema.ts, auth.schema.ts)
│ │ └── types/ # 推論された型 (export type User = z.infer<...>)
│ └── package.json
3. ステップバイステップの開発ワークフロー
フルスタックの機能を実装する際は、次のフェーズを順に実行してください:
フェーズ 1: 共有契約とスキーマの定義
shared/またはcommon/ディレクトリに、リクエストペイロードとレスポンスエンティティを表すZodスキーマを定義します。- 推論された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スキーマを使用したバリデーションミドルウェアを適用し、不正な入力をHTTP 400で早期に拒否します。
- サービスレイヤー: サービス クラスまたはモジュールに純粋なビジネスロジックを記述します。データベースクエリをHTTPの概念(
req、res)から分離します。 - コントローラー: HTTP入力の抽出、サービスの呼び出し、および
sendSuccess()を使用した標準化されたレスポンスの返却を処理します。 - エラーハンドリング: カスタムエラー(
NotFoundError、BadRequestError、UnauthorizedError)を使用し、一元化されたエラーミドルウェアがそれをキャッチしてレスポンスをフォーマットするようにします。
フェーズ 3: フロントエンドUIコンポーネントの構築
- デザインシステムトークン: 色、タイポグラフィ、スペーシング、シャドウ用のCSSカスタムプロパティまたはデザインシステム変数が存在することを確認します。
- アトミック / UIプリミティブ: キーボードナビゲーションとARIA属性を持つベースUIコンポーネント(ボタン、入力フィールド、バッジ、ダイアログ)を構築または再利用します。
- 機能ビュー / ページ: 明確な視覚的階層、十分な余白、モダンなタイポグラフィ、レスポンシブグリッド/フレックスボックスレイアウトで複合コンポーネントを組み立てます。
- 状態 & マイクロインタラクション:
- ホバー、アクティブ、フォーカス可視、無効状態の視覚的なフィードバックを提供します。
- 読み込み中スケルトン、スピナーインジケーター、送信保留中の無効ボタンを含めます。
フェーズ 4: 統合とデータフェッチング
- APIクライアント: ベースURL、タイムアウト、認証情報、インターセプターで設定された一元化されたフェッチラッパーまたはAxiosインスタンスを使用します。
- サーバー状態: TanStack Query(React Query) または標準フックを使用してデータのクエリとミューテーションを行います。
- 成功したミューテーション時にキャッシュを無効化します。
- 必要に応じて、迅速なユーザーエクスペリエンスのために楽観的更新を行います。
- フォーム処理: React Hook Form + Zod リゾルバーを使用してフォームを統合し、クライアントとサーバー間でまったく同じ検証ルールを共有します。
フェーズ 5: 検証と強化
- バックエンド検証: 単体テスト/統合テスト、またはcurl/fetchを使用してエンドポイントをテストし、エッジケース(無効な入力、欠落したトークン、重複キー)を検証します。
- フロントエンド検証: デスクトップ、タブレット、モバイルのビューポート幅でのレスポンシブ対応を確認します。
- セキュリティチェック: 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. リファレンスと詳細情報
詳細なアーキテクチャパターンについては、以下を参照してください: