コンテンツにスキップ

コーディングルール

命名規則

対象 規則 例
変数・関数 camelCase getCandidateById
型・インターフェース PascalCase ArrangeSettings
ファイル名 kebab-case survey-import.ts / use-project-arrange.ts
定数 UPPER_SNAKE_CASE VALID_STATUS_TRANSITIONS
DB テーブル・カラム snake_case(複数形テーブル) project_candidates.external_user_id
API の公開フィールド snake_case interview_duration_minutes
サービス以降の内部コード camelCase interviewDurationMinutes
React コンポーネント PascalCase(ファイルは kebab-case) SegmentSidebar / segment-sidebar.tsx
Orval 生成フック パスから自動命名(変更しない) useGetApiV1ProjectsProjectId

snake_case ↔ camelCase の変換位置

API の公開フィールドは snake_case、内部は camelCase。この変換はルートハンドラ内で明示的に行う(data.interview_duration_minutes → interviewDurationMinutes)。service 層に snake_case を持ち込まない。

フォーマッター・リンター

# backend
bun run format        # prettier --write 'src/**/*.ts'
bun run lint          # eslint src/
bun run lint:fix
bun run check-types   # tsc --noEmit

# frontend
bun run format
bun run lint
bun run check-types

設定ファイル:

ファイル 内容
.prettierrc.mjs semi: true / singleQuote: true / printWidth: 100 / trailingComma: 'es5' / tabWidth: 2
eslint.config.js(backend) @typescript-eslint recommended + 独自ルール
eslint.config.mjs(frontend) eslint-config-next + prettier

主な ESLint ルール(backend):

ルール 設定
@typescript-eslint/no-unused-vars error。_ プレフィックスは除外
@typescript-eslint/no-explicit-any warn
no-console warn(console.warn / console.error のみ許可。通常は logger を使う)
@typescript-eslint/explicit-function-return-type off

両リポジトリとも husky + lint-staged により、コミット時に src/** へ prettier --write と eslint --fix が走る。

backend の実装規約

import は ESM の .js 拡張子付き

import { response } from '../lib/index.js';        // ✅
import { response } from '../lib/index';           // ❌

TypeScript のソースを指す場合も .js と書く。

レスポンスは必ず response ヘルパーを通す

src/lib/response.ts の response.success / created / paginated / error を使う。ボディは必ず封筒形式になる。

return response.success(c, project);
return response.created(c, project);
return response.paginated(c, items, pagination);

c.json() を直接呼ばない。

エラーは throw するだけ

middlewares/error-handler.ts が status / code / メッセージに変換する。ハンドラ内で try-catch してレスポンスを組み立てない。

if (!project) throw new NotFoundError('Project');           // 404
throw new InvalidStatusTransitionError(oldStatus, newStatus); // 400
定義場所 内容
src/lib/errors.ts 汎用(HTTP / DB / 外部 API / Databricks)
src/error.ts 業務固有(Google API、スケジューリングトークン、枠競合、ステータス遷移など)

isOperational が false のものはクライアントに詳細を出さない(開発時のみ stack を含める)。

層をまたいだ呼び出しをしない

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

  • ルートから Drizzle を直接呼ばない
  • service に Hono の Context を渡さない
  • repository に業務判断を書かない

環境変数は getEnvConfig() 経由

process.env を直接読まない。特に PROTOTYPE_MODE の判定は専用ヘルパーを使う。

if (isEmailEnabled()) { ... }      // ✅
if (isCalendarEnabled()) { ... }   // ✅
if (process.env.PROTOTYPE_MODE) { ... }  // ❌

例外はバッチ(src/batch/*.ts)。アプリ用の必須環境変数が揃わない環境で動かすため、DATABASE_URL だけを直接読む。

ルート定義はハンドラと対で書く

const getProjectRoute = createRoute({
  method: 'get',
  path: '/{projectId}',
  tags: ['Projects'],
  summary: 'プロジェクト詳細取得',
  security: [{ bearerAuth: [] }],
  request: { params: ProjectIdParamSchema },
  responses: { 200: { ... }, 404: commonResponses[404] },
});

projectsRoutes.openapi(getProjectRoute, async (c) => { ... });

エラーレスポンスは commonResponses を再利用する。

frontend の実装規約

src/api/** は生成物。手で編集しない

API を変えたら backend を起動して bun run generate-api を実行する。

認証トークンの出入口は src/lib/auth.ts のみ

localStorage を直接触らない。setAuthToken / getAuthToken / removeAuthToken を使う。

API レスポンスは apiData() で取り出す

const { data } = useGetApiV1Projects();
const projects = apiData<GetApiV1Projects200>(data);

response.data.data を手で辿らない。

状態の置き場所

種類 置き場所
サーバ状態 React Query(Orval 生成フック)
ローカル UI 状態 各コンポーネントの useState
共有プロバイダ src/components/providers.tsx
アレンジフローの状態 use-project-arrange.ts に集約。画面は薄く保つ

クラス名の合成は cn()

import { cn } from '@/lib/utils';
<div className={cn('px-4', isActive && 'bg-blue-500')} />

型の二重定義について

ArrangeSettings(projects.arrange_settings JSON カラムの中身)は、OpenAPI では内部構造を表現できないため以下の 2 か所に二重定義されている。

ファイル 用途
backend src/db/schema.ts DB・service 側
frontend src/types/arrange-settings.ts 画面側

片方を変えたら必ずもう片方も直す。

PR とレビューの基準

  • 1 PR は 1 つの目的に絞る
  • 依頼されていない機能追加・リファクタ・抽象化を混ぜない
  • API を変更した PR では、bun run generate-api の結果(src/api/** の差分)を同じ PR に含める
  • DB スキーマを変更した PR では、データベース のドキュメントも更新する
  • セルフレビューをしてからレビュー依頼する
  • git push --force 系は行わない

アンチパターン

  • any 型の多用(no-explicit-any は warn だが、原則使わない)
  • console.log の直書き(logger を使う。console.warn / console.error のみ許可)
  • ルートハンドラでの Drizzle 直呼び
  • c.json() の直接呼び出し(response ヘルパーを使う)
  • ハンドラ内での try-catch によるエラーレスポンス組み立て(throw する)
  • process.env の直読み
  • 生成物(src/api/**)の手編集
  • 環境変数を src/config/env.ts にだけ追加して .env.example を忘れる
  • マジックナンバーの直書き(定数に切り出す)
  • 深いネスト(早期リターンで対応する)