コンテンツにスキップ

ディレクトリ構成

リポジトリ全体

arrange/
├── heineken-interview-arrange-backend/    # Bun + Hono の REST API
├── heineken-interview-arrange-frontend/   # Next.js App Router の管理画面
└── heineken-interview-arrange-docs/       # 本ドキュメント(MkDocs)

両者は OpenAPI 経由でのみ結合している。バックエンドがルート定義から /openapi.json を自動生成し、フロントは Orval でそこから React Query フックと型を生成する。

backend

heineken-interview-arrange-backend/
├── src/
│   ├── app.ts             # Hono アプリの組み立て(CORS・OpenAPI・Swagger UI・404/エラー)
│   ├── server.ts          # エントリポイント
│   ├── error.ts           # 業務固有の AppError サブクラス
│   ├── routes/
│   │   ├── index.ts
│   │   └── v1/            # HTTP 層。createRoute() で OpenAPI 定義とハンドラを対で書く
│   ├── schemas/           # リクエスト/レスポンスの Zod スキーマ(公開フィールドは snake_case)
│   ├── services/          # ビジネスロジック
│   ├── repositories/      # Drizzle クエリのみ
│   ├── db/
│   │   └── schema.ts      # 全テーブル・全 enum・ドメイン型の単一定義元
│   ├── lib/               # response / errors / logger / pagination / date / databricks-client など
│   ├── middlewares/       # protected-route / error-handler / request-id / winston-logger
│   ├── config/            # env / database / google / business-hours
│   ├── types/             # インポート関連の型
│   └── batch/             # API とは独立して動くバッチ(import / databricks-meta-sync / pii-cleanup)
├── __tests__/
│   └── unit/services/     # Vitest の単体テスト
├── scripts/               # setup.sh / migrate.ts / seed.ts
├── drizzle.config.ts
├── docker-compose.yml     # PostgreSQL 17
├── Dockerfile             # アプリ(Lambda + aws-lambda-adapter)
├── Dockerfile.migration   # マイグレーション Lambda
└── Dockerfile.sync        # 同期バッチ(ECS Fargate)

レイヤ構成

routes → services → repositories → Drizzle の一方向依存。

ディレクトリ 責務 してはいけないこと
src/routes/v1/ HTTP 層。OpenAPI 定義、認証・ロール制御、snake_case ↔ camelCase の変換 ビジネスロジックを書く、Drizzle を直接呼ぶ
src/schemas/ Zod スキーマ。API の公開フィールドは snake_case DB のカラム名をそのまま公開する
src/services/ ビジネスロジック。export * as xxxService で services/index.ts から名前空間として再エクスポート Hono の Context に依存する
src/repositories/ Drizzle クエリのみ。失敗は DatabaseQueryError などに包んで投げる 業務判断を行う
src/db/schema.ts 全テーブル・enum・$inferSelect / $inferInsert 由来の型の単一定義元 複数ファイルに分割する

ESM の拡張子

import は .js 拡張子付きが必須(from '../lib/index.js')。TypeScript のソースを指す場合も .js と書く。

認証・ロール制御の適用箇所

ルータ単位で適用する。個別ハンドラでは書かない。

candidatesRoutes.use('*', protectedRoute());
candidatesRoutes.on(['POST', 'PATCH', 'PUT', 'DELETE'], ['/*'], requireRole('member'));

frontend

heineken-interview-arrange-frontend/
├── src/
│   ├── app/
│   │   ├── (dashboard)/          # 認証が要る管理画面
│   │   │   ├── page.tsx          # ダッシュボード
│   │   │   ├── projects/         # プロジェクト一覧・作成・詳細(アレンジフロー)
│   │   │   └── surveys/          # アンケート閲覧
│   │   ├── login/                # ログイン
│   │   └── scheduling/[token]/   # 候補者向け公開ページ(認証なし)
│   ├── api/                      # Orval 生成物。手で編集しない
│   │   ├── custom-fetch.ts       # Bearer 付与・{ data, status, headers } 形で返す
│   │   ├── endpoints/            # タグ別のフック
│   │   └── models/               # 型定義
│   ├── components/
│   │   ├── ui/                   # 汎用 UI(button / table / modal / slot-calendar など)
│   │   ├── layout/               # app-layout / header / sidebar
│   │   ├── projects/             # arrange-steps / step1-form / dashboard-stats
│   │   ├── step2/                # segment-sidebar / candidate-table / question-filter
│   │   ├── candidates/           # candidate-row / summary-bar / status-helpers
│   │   └── providers.tsx         # React Query などの共有プロバイダ
│   ├── lib/                      # auth / utils / candidate-status / response-filter
│   └── types/
│       └── arrange-settings.ts   # backend と二重定義されるアレンジ設定の型
├── orval.config.ts
├── next.config.ts
└── Dockerfile

各ディレクトリの説明

ディレクトリ 説明
src/app/(dashboard)/ 認証が必要な管理画面。ルートグループなので URL には現れない
src/app/scheduling/[token]/ トークンのみでアクセスする公開ページ。認証しない
src/api/ Orval 生成物。useGetApiV1ProjectsProjectId のようにパスから命名される
src/components/ui/ 再利用可能な UI コンポーネント
src/lib/ 認証トークンの出入口(auth.ts)とユーティリティ
src/types/ OpenAPI で表現されない型(JSON カラムの中身など)

API レスポンスの取り出し

custom-fetch.ts が { data, status, headers } 形で返し、その data が API のエンベロープ({ success, data, ... })なので、画面側は response.data.data を辿ることになる。lib/utils.ts の apiData() ヘルパーを使う。

const { data } = useGetApiV1Projects();
const projects = apiData<GetApiV1Projects200>(data);
// projects?.data → Project[]

新ファイルを追加するとき

backend

追加するもの 置き場所
新しいエンドポイント src/routes/v1/<resource>.ts(既存ルータに追記、または新規作成して routes/v1/index.ts に登録)
リクエスト/レスポンス定義 src/schemas/<resource>.ts
ビジネスロジック src/services/<resource>.ts → services/index.ts に再エクスポート
DB クエリ src/repositories/<resource>.ts
テーブル・enum・ドメイン型 src/db/schema.ts(単一ファイル)
環境変数 src/config/env.ts と .env.example の両方
汎用エラー src/lib/errors.ts
業務固有エラー src/error.ts
バッチ src/batch/<name>.ts

frontend

追加するもの 置き場所
画面 src/app/(dashboard)/**/page.tsx
汎用 UI コンポーネント src/components/ui/
画面固有コンポーネント src/components/<画面名>/
API 呼び出し 追加しない(bun run generate-api で src/api/** を再生成する)
型定義 src/types/