@einja/dev-cli 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +179 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +49 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +243 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +23 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/sync.d.ts +7 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +294 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/sync.test.d.ts +2 -0
- package/dist/commands/sync.test.d.ts.map +1 -0
- package/dist/commands/sync.test.js +593 -0
- package/dist/commands/sync.test.js.map +1 -0
- package/dist/commands/task-loop.d.ts +11 -0
- package/dist/commands/task-loop.d.ts.map +1 -0
- package/dist/commands/task-loop.js +81 -0
- package/dist/commands/task-loop.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/file-system.d.ts +39 -0
- package/dist/lib/file-system.d.ts.map +1 -0
- package/dist/lib/file-system.js +79 -0
- package/dist/lib/file-system.js.map +1 -0
- package/dist/lib/mcp-config.d.ts +43 -0
- package/dist/lib/mcp-config.d.ts.map +1 -0
- package/dist/lib/mcp-config.js +109 -0
- package/dist/lib/mcp-config.js.map +1 -0
- package/dist/lib/mcp-config.test.d.ts +2 -0
- package/dist/lib/mcp-config.test.d.ts.map +1 -0
- package/dist/lib/mcp-config.test.js +285 -0
- package/dist/lib/mcp-config.test.js.map +1 -0
- package/dist/lib/merger.d.ts +41 -0
- package/dist/lib/merger.d.ts.map +1 -0
- package/dist/lib/merger.js +164 -0
- package/dist/lib/merger.js.map +1 -0
- package/dist/lib/preset-update/cli-repo-detector.d.ts +35 -0
- package/dist/lib/preset-update/cli-repo-detector.d.ts.map +1 -0
- package/dist/lib/preset-update/cli-repo-detector.js +83 -0
- package/dist/lib/preset-update/cli-repo-detector.js.map +1 -0
- package/dist/lib/preset-update/cli-repo-detector.test.d.ts +2 -0
- package/dist/lib/preset-update/cli-repo-detector.test.d.ts.map +1 -0
- package/dist/lib/preset-update/cli-repo-detector.test.js +120 -0
- package/dist/lib/preset-update/cli-repo-detector.test.js.map +1 -0
- package/dist/lib/preset-update/file-copier.d.ts +59 -0
- package/dist/lib/preset-update/file-copier.d.ts.map +1 -0
- package/dist/lib/preset-update/file-copier.js +220 -0
- package/dist/lib/preset-update/file-copier.js.map +1 -0
- package/dist/lib/preset-update/file-copier.test.d.ts +2 -0
- package/dist/lib/preset-update/file-copier.test.d.ts.map +1 -0
- package/dist/lib/preset-update/file-copier.test.js +297 -0
- package/dist/lib/preset-update/file-copier.test.js.map +1 -0
- package/dist/lib/preset-update/preset-finder.d.ts +39 -0
- package/dist/lib/preset-update/preset-finder.d.ts.map +1 -0
- package/dist/lib/preset-update/preset-finder.js +92 -0
- package/dist/lib/preset-update/preset-finder.js.map +1 -0
- package/dist/lib/preset-update/preset-finder.test.d.ts +2 -0
- package/dist/lib/preset-update/preset-finder.test.d.ts.map +1 -0
- package/dist/lib/preset-update/preset-finder.test.js +128 -0
- package/dist/lib/preset-update/preset-finder.test.js.map +1 -0
- package/dist/lib/preset.d.ts +14 -0
- package/dist/lib/preset.d.ts.map +1 -0
- package/dist/lib/preset.js +52 -0
- package/dist/lib/preset.js.map +1 -0
- package/dist/lib/sync/backup-manager.d.ts +50 -0
- package/dist/lib/sync/backup-manager.d.ts.map +1 -0
- package/dist/lib/sync/backup-manager.js +117 -0
- package/dist/lib/sync/backup-manager.js.map +1 -0
- package/dist/lib/sync/backup-manager.test.d.ts +2 -0
- package/dist/lib/sync/backup-manager.test.d.ts.map +1 -0
- package/dist/lib/sync/backup-manager.test.js +155 -0
- package/dist/lib/sync/backup-manager.test.js.map +1 -0
- package/dist/lib/sync/batch-processor.d.ts +27 -0
- package/dist/lib/sync/batch-processor.d.ts.map +1 -0
- package/dist/lib/sync/batch-processor.js +46 -0
- package/dist/lib/sync/batch-processor.js.map +1 -0
- package/dist/lib/sync/batch-processor.test.d.ts +2 -0
- package/dist/lib/sync/batch-processor.test.d.ts.map +1 -0
- package/dist/lib/sync/batch-processor.test.js +110 -0
- package/dist/lib/sync/batch-processor.test.js.map +1 -0
- package/dist/lib/sync/category-validator.d.ts +36 -0
- package/dist/lib/sync/category-validator.d.ts.map +1 -0
- package/dist/lib/sync/category-validator.js +46 -0
- package/dist/lib/sync/category-validator.js.map +1 -0
- package/dist/lib/sync/category-validator.test.d.ts +2 -0
- package/dist/lib/sync/category-validator.test.d.ts.map +1 -0
- package/dist/lib/sync/category-validator.test.js +89 -0
- package/dist/lib/sync/category-validator.test.js.map +1 -0
- package/dist/lib/sync/conflict-reporter.d.ts +57 -0
- package/dist/lib/sync/conflict-reporter.d.ts.map +1 -0
- package/dist/lib/sync/conflict-reporter.js +81 -0
- package/dist/lib/sync/conflict-reporter.js.map +1 -0
- package/dist/lib/sync/conflict-reporter.test.d.ts +2 -0
- package/dist/lib/sync/conflict-reporter.test.d.ts.map +1 -0
- package/dist/lib/sync/conflict-reporter.test.js +132 -0
- package/dist/lib/sync/conflict-reporter.test.js.map +1 -0
- package/dist/lib/sync/diff-engine.d.ts +28 -0
- package/dist/lib/sync/diff-engine.d.ts.map +1 -0
- package/dist/lib/sync/diff-engine.js +118 -0
- package/dist/lib/sync/diff-engine.js.map +1 -0
- package/dist/lib/sync/diff-engine.test.d.ts +2 -0
- package/dist/lib/sync/diff-engine.test.d.ts.map +1 -0
- package/dist/lib/sync/diff-engine.test.js +133 -0
- package/dist/lib/sync/diff-engine.test.js.map +1 -0
- package/dist/lib/sync/file-filter.d.ts +40 -0
- package/dist/lib/sync/file-filter.d.ts.map +1 -0
- package/dist/lib/sync/file-filter.js +171 -0
- package/dist/lib/sync/file-filter.js.map +1 -0
- package/dist/lib/sync/file-filter.test.d.ts +2 -0
- package/dist/lib/sync/file-filter.test.d.ts.map +1 -0
- package/dist/lib/sync/file-filter.test.js +179 -0
- package/dist/lib/sync/file-filter.test.js.map +1 -0
- package/dist/lib/sync/hash-cache.d.ts +34 -0
- package/dist/lib/sync/hash-cache.d.ts.map +1 -0
- package/dist/lib/sync/hash-cache.js +51 -0
- package/dist/lib/sync/hash-cache.js.map +1 -0
- package/dist/lib/sync/hash-cache.test.d.ts +2 -0
- package/dist/lib/sync/hash-cache.test.d.ts.map +1 -0
- package/dist/lib/sync/hash-cache.test.js +110 -0
- package/dist/lib/sync/hash-cache.test.js.map +1 -0
- package/dist/lib/sync/integration.test.d.ts +2 -0
- package/dist/lib/sync/integration.test.d.ts.map +1 -0
- package/dist/lib/sync/integration.test.js +317 -0
- package/dist/lib/sync/integration.test.js.map +1 -0
- package/dist/lib/sync/marker-processor.d.ts +54 -0
- package/dist/lib/sync/marker-processor.d.ts.map +1 -0
- package/dist/lib/sync/marker-processor.js +208 -0
- package/dist/lib/sync/marker-processor.js.map +1 -0
- package/dist/lib/sync/marker-processor.test.d.ts +2 -0
- package/dist/lib/sync/marker-processor.test.d.ts.map +1 -0
- package/dist/lib/sync/marker-processor.test.js +245 -0
- package/dist/lib/sync/marker-processor.test.js.map +1 -0
- package/dist/lib/sync/metadata-manager.d.ts +46 -0
- package/dist/lib/sync/metadata-manager.d.ts.map +1 -0
- package/dist/lib/sync/metadata-manager.js +129 -0
- package/dist/lib/sync/metadata-manager.js.map +1 -0
- package/dist/lib/sync/metadata-manager.test.d.ts +2 -0
- package/dist/lib/sync/metadata-manager.test.d.ts.map +1 -0
- package/dist/lib/sync/metadata-manager.test.js +137 -0
- package/dist/lib/sync/metadata-manager.test.js.map +1 -0
- package/dist/lib/sync/performance.test.d.ts +2 -0
- package/dist/lib/sync/performance.test.d.ts.map +1 -0
- package/dist/lib/sync/performance.test.js +126 -0
- package/dist/lib/sync/performance.test.js.map +1 -0
- package/dist/types/index.d.ts +59 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/preset-update.d.ts +106 -0
- package/dist/types/preset-update.d.ts.map +1 -0
- package/dist/types/preset-update.js +5 -0
- package/dist/types/preset-update.js.map +1 -0
- package/dist/types/sync.d.ts +169 -0
- package/dist/types/sync.d.ts.map +1 -0
- package/dist/types/sync.js +19 -0
- package/dist/types/sync.js.map +1 -0
- package/package.json +72 -0
- package/presets/minimal/.claude/agents/einja/docs/docs-updater.md +161 -0
- package/presets/minimal/.claude/agents/einja/frontend/design-engineer.md +685 -0
- package/presets/minimal/.claude/agents/einja/frontend/frontend-architect.md +747 -0
- package/presets/minimal/.claude/agents/einja/frontend/frontend-coder.md +441 -0
- package/presets/minimal/.claude/agents/einja/git/conflict-resolver.md +148 -0
- package/presets/minimal/.claude/agents/einja/specs/spec-design-generator.md +462 -0
- package/presets/minimal/.claude/agents/einja/specs/spec-qa-generator.md +466 -0
- package/presets/minimal/.claude/agents/einja/specs/spec-requirements-generator.md +416 -0
- package/presets/minimal/.claude/agents/einja/specs/spec-tasks-generator.md +608 -0
- package/presets/minimal/.claude/agents/einja/task/task-committer.md +82 -0
- package/presets/minimal/.claude/agents/einja/task/task-executer.md +352 -0
- package/presets/minimal/.claude/agents/einja/task/task-modification-analyzer.md +369 -0
- package/presets/minimal/.claude/agents/einja/task/task-qa.md +74 -0
- package/presets/minimal/.claude/agents/einja/task/task-reviewer.md +169 -0
- package/presets/minimal/.claude/commands/einja/frontend-implement.md +322 -0
- package/presets/minimal/.claude/commands/einja/spec-create.md +254 -0
- package/presets/minimal/.claude/commands/einja/start-dev.md +98 -0
- package/presets/minimal/.claude/commands/einja/sync-cursor-commands.md +203 -0
- package/presets/minimal/.claude/commands/einja/task-exec.md +390 -0
- package/presets/minimal/.claude/commands/einja/update-docs-by-task-specs.md +448 -0
- package/presets/minimal/.claude/hooks/einja/biome-format.sh +49 -0
- package/presets/minimal/.claude/hooks/einja/design-doc-check.sh +61 -0
- package/presets/minimal/.claude/hooks/einja/detect-secrets.sh +62 -0
- package/presets/minimal/.claude/hooks/einja/large-file-warning.sh +42 -0
- package/presets/minimal/.claude/hooks/einja/playwright-resize.sh +36 -0
- package/presets/minimal/.claude/hooks/einja/typecheck.sh +37 -0
- package/presets/minimal/.claude/hooks/einja/unset-volta-recursion.sh +32 -0
- package/presets/minimal/.claude/hooks/einja/validate-git-commit.sh +239 -0
- package/presets/minimal/.claude/hooks/einja/warn-index-ts.sh +34 -0
- package/presets/minimal/.claude/hooks/einja/warn-relative-import.sh +48 -0
- package/presets/minimal/.claude/settings.json +174 -0
- package/presets/minimal/.claude/skills/einja/api-development/SKILL.md +14 -0
- package/presets/minimal/.claude/skills/einja/backend-architecture/SKILL.md +14 -0
- package/presets/minimal/.claude/skills/einja/coding-standards/SKILL.md +120 -0
- package/presets/minimal/.claude/skills/einja/coding-standards/reference/naming-conventions.md +107 -0
- package/presets/minimal/.claude/skills/einja/coding-standards/reference/prohibited-patterns.md +169 -0
- package/presets/minimal/.claude/skills/einja/coding-standards/reference/typescript-rules.md +247 -0
- package/presets/minimal/.claude/skills/einja/component-design/SKILL.md +109 -0
- package/presets/minimal/.claude/skills/einja/component-design/reference/directory-structure.md +117 -0
- package/presets/minimal/.claude/skills/einja/component-design/reference/props-patterns.md +159 -0
- package/presets/minimal/.claude/skills/einja/component-design/reference/styling-guide.md +200 -0
- package/presets/minimal/.claude/skills/einja/conflict-resolver/SKILL.md +190 -0
- package/presets/minimal/.claude/skills/einja/frontend-development/SKILL.md +14 -0
- package/presets/minimal/.claude/skills/einja/general-context-loader/SKILL.md +254 -0
- package/presets/minimal/.claude/skills/einja/output-format/SKILL.md +137 -0
- package/presets/minimal/.claude/skills/einja/spec-context-loader/SKILL.md +177 -0
- package/presets/minimal/.claude/skills/einja/task-commit/SKILL.md +269 -0
- package/presets/minimal/.claude/skills/einja/task-qa/SKILL.md +306 -0
- package/presets/minimal/.claude/skills/einja/task-qa/reference/failure-patterns.md +69 -0
- package/presets/minimal/.claude/skills/einja/task-qa/reference/troubleshooting.md +65 -0
- package/presets/minimal/.claude/skills/einja/task-qa/reference/usage-patterns.md +52 -0
- package/presets/minimal/.claude/skills/einja/task-qa/templates/qa-test-template.md +128 -0
- package/presets/minimal/preset.yaml +111 -0
- package/presets/minimal/symlinks.json +45 -0
- package/scaffolds/.mcp.json +45 -0
- package/scaffolds/CLAUDE.md.template +318 -0
- package/scaffolds/steering/README.md +170 -0
- package/scaffolds/steering/acceptance-criteria-and-qa-guide.md +415 -0
- package/scaffolds/steering/architecture.md +481 -0
- package/scaffolds/steering/branch-strategy.md +362 -0
- package/scaffolds/steering/commit-rules.md +217 -0
- package/scaffolds/steering/db-schema-design.md +609 -0
- package/scaffolds/steering/development/api-development.md +783 -0
- package/scaffolds/steering/development/backend-architecture.md +731 -0
- package/scaffolds/steering/development/frontend-development.md +1537 -0
- package/scaffolds/steering/development/review-guidelines.md +365 -0
- package/scaffolds/steering/development/testing-strategy.md +819 -0
- package/scaffolds/steering/development-workflow.md +429 -0
- package/scaffolds/steering/infrastructure/deployment.md +277 -0
- package/scaffolds/steering/infrastructure/environment-variables.md +298 -0
- package/scaffolds/steering/product.md +540 -0
- package/scaffolds/steering/task-management.md +367 -0
- package/templates/README.md +159 -0
- package/templates/design-simple.md.template +172 -0
- package/templates/design.md.template +327 -0
- package/templates/qa-test.md.template +125 -0
- package/templates/requirements.md.template +254 -0
|
@@ -0,0 +1,783 @@
|
|
|
1
|
+
# API開発ガイド
|
|
2
|
+
|
|
3
|
+
## 概要
|
|
4
|
+
|
|
5
|
+
このドキュメントでは、Honoを使用したAPI開発の実装ガイドラインと、フロントエンドとの統合方法を説明します。
|
|
6
|
+
|
|
7
|
+
モノレポ全体で統一されたAPI設計パターンを採用し、型安全性とメンテナンス性を確保します。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 目次
|
|
12
|
+
|
|
13
|
+
1. [Hono API実装ルール](#1-hono-api実装ルール)
|
|
14
|
+
- [メソッドチェーンパターン](#メソッドチェーンパターン)
|
|
15
|
+
- [ミドルウェアと型推論の注意点](#ミドルウェアと型推論の注意点) ⚠️
|
|
16
|
+
- [basePathとHono Clientの関係](#basepathとhono-clientの関係) ⚠️
|
|
17
|
+
2. [Web APIエンドポイント一覧](#2-web-apiエンドポイント一覧)
|
|
18
|
+
3. [Admin APIエンドポイント一覧](#3-admin-apiエンドポイント一覧)
|
|
19
|
+
4. [Cron Worker CLIコマンド](#4-cron-worker-cliコマンド)
|
|
20
|
+
5. [バリデーション戦略](#5-バリデーション戦略)
|
|
21
|
+
6. [エラーハンドリング](#6-エラーハンドリング)
|
|
22
|
+
7. [フロントエンド統合パターン](#7-フロントエンド統合パターン)
|
|
23
|
+
8. [認証とミドルウェア](#8-認証とミドルウェア)
|
|
24
|
+
9. [レスポンス設計](#9-レスポンス設計)
|
|
25
|
+
10. [環境変数管理](#10-環境変数管理)
|
|
26
|
+
11. [実装例](#11-実装例)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 1. Hono API実装ルール
|
|
31
|
+
|
|
32
|
+
### Honoアプリケーション構造
|
|
33
|
+
|
|
34
|
+
Honoは型安全なWebフレームワークで、すべてのNext.js APIルートで使用します。
|
|
35
|
+
|
|
36
|
+
**エントリーポイント**:
|
|
37
|
+
```
|
|
38
|
+
apps/web/app/api/[[...route]]/route.ts # Web API
|
|
39
|
+
apps/admin/app/api/[[...route]]/route.ts # Admin API
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**ルート定義の配置**:
|
|
43
|
+
```
|
|
44
|
+
apps/web/server/routes/ # Webアプリ用ルート定義
|
|
45
|
+
apps/admin/server/routes/ # 管理画面用ルート定義
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### メソッドチェーンパターン
|
|
49
|
+
|
|
50
|
+
Honoでは、**必ずメソッドチェーン形式**でルートを定義します。
|
|
51
|
+
|
|
52
|
+
**重要: メソッドチェーンを使用する理由**
|
|
53
|
+
|
|
54
|
+
Hono Clientの型推論は `typeof app` から型情報を抽出します。メソッドチェーンを使用しない場合、TypeScriptが各ルート定義の返り値型を追跡できず、`AppType`に完全なルート情報が含まれません。
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// ❌ NG: 個別呼び出し - 型推論が損なわれる
|
|
58
|
+
const app = new Hono()
|
|
59
|
+
app.get('/posts', handler1) // 返り値が破棄される
|
|
60
|
+
app.post('/posts', handler2) // 返り値が破棄される
|
|
61
|
+
export type AppType = typeof app // ルート情報が不完全
|
|
62
|
+
|
|
63
|
+
// ✅ OK: メソッドチェーン - 完全な型推論
|
|
64
|
+
const app = new Hono()
|
|
65
|
+
.get('/posts', handler1)
|
|
66
|
+
.post('/posts', handler2)
|
|
67
|
+
export type AppType = typeof app // 全ルート情報を含む
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
メソッドチェーンにより、Hono Client (`hc<AppType>`) でエンドツーエンドの型安全なAPI呼び出しが実現できます。
|
|
71
|
+
|
|
72
|
+
### ミドルウェアと型推論の注意点
|
|
73
|
+
|
|
74
|
+
**サブルート内で`.use()`を使うと型推論が壊れる。メインアプリ側で適用すること。**
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// ❌ NG: サブルート内で.use() → 型が ClientRequest<{}> になる
|
|
78
|
+
export const adminUserRoutes = new Hono()
|
|
79
|
+
.use("*", adminAuthMiddleware)
|
|
80
|
+
.delete("/:id", handler)
|
|
81
|
+
|
|
82
|
+
// ✅ OK: メインアプリ側で.use()を適用
|
|
83
|
+
const app = new Hono()
|
|
84
|
+
.basePath("/api")
|
|
85
|
+
.use("/admin/*", adminAuthMiddleware) // ← ここで適用
|
|
86
|
+
.route("/admin", adminApp)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### basePathとHono Clientの関係
|
|
90
|
+
|
|
91
|
+
`basePath("/api")` を使用する場合、クライアント側も `api` を含めてアクセスする。
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// サーバー: basePath("/api") を設定
|
|
95
|
+
const app = new Hono().basePath("/api").route("/posts", postRoutes)
|
|
96
|
+
|
|
97
|
+
// クライアント: api を含める
|
|
98
|
+
apiClient.api.posts.$get() // ✅ OK
|
|
99
|
+
apiClient.posts.$get() // ❌ NG(型エラー)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**使用例**:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// apps/web/server/routes/postRoutes.ts
|
|
106
|
+
import { Hono } from 'hono'
|
|
107
|
+
import { zValidator } from '@hono/zod-validator'
|
|
108
|
+
import { createPostSchema } from '@repo/server-core/domain/validators/post'
|
|
109
|
+
|
|
110
|
+
const app = new Hono()
|
|
111
|
+
|
|
112
|
+
app
|
|
113
|
+
.get('/posts', async (c) => {
|
|
114
|
+
// GET /api/posts - 投稿一覧取得
|
|
115
|
+
const page = Number(c.req.query('page') || '1')
|
|
116
|
+
const limit = Number(c.req.query('limit') || '10')
|
|
117
|
+
// UseCase呼び出し...
|
|
118
|
+
return c.json({ posts, total })
|
|
119
|
+
})
|
|
120
|
+
.post('/posts', zValidator('json', createPostSchema), async (c) => {
|
|
121
|
+
// POST /api/posts - 投稿作成
|
|
122
|
+
const data = c.req.valid('json')
|
|
123
|
+
// UseCase呼び出し...
|
|
124
|
+
return c.json({ post }, 201)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
export default app
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### zValidatorの統合
|
|
131
|
+
|
|
132
|
+
**必須**: すべてのリクエストボディとクエリパラメータは、zValidatorでバリデーションを行う。
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { zValidator } from '@hono/zod-validator'
|
|
136
|
+
import { createPostSchema } from '@repo/server-core/domain/validators/post'
|
|
137
|
+
|
|
138
|
+
app.post('/posts', zValidator('json', createPostSchema), async (c) => {
|
|
139
|
+
const data = c.req.valid('json') // 型安全にバリデート済みデータを取得
|
|
140
|
+
// data は CreatePostInput 型として推論される
|
|
141
|
+
})
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### ルートハンドラの構造
|
|
145
|
+
|
|
146
|
+
すべてのルートハンドラは、以下のパターンに従います:
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
async (c) => {
|
|
150
|
+
// 1. リクエストパラメータ取得(クエリ、パス、ボディ)
|
|
151
|
+
const data = c.req.valid('json')
|
|
152
|
+
const id = c.req.param('id')
|
|
153
|
+
|
|
154
|
+
// 2. UseCaseの呼び出し
|
|
155
|
+
const result = await postUseCases.create(data)
|
|
156
|
+
|
|
157
|
+
// 3. Result型の処理
|
|
158
|
+
if (!result.isSuccess) {
|
|
159
|
+
// エラーハンドリング(後述)
|
|
160
|
+
return c.json({ error: result.error.message }, 400)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 4. レスポンス返却
|
|
164
|
+
return c.json({ post: result.value }, 201)
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## 2. Web APIエンドポイント一覧
|
|
171
|
+
|
|
172
|
+
### ヘルスチェック
|
|
173
|
+
|
|
174
|
+
| メソッド | エンドポイント | 説明 | リクエスト | レスポンス | 認証 |
|
|
175
|
+
|---------|---------------|------|-----------|-----------|------|
|
|
176
|
+
| GET | `/api/health` | システム稼働確認 | - | `{status: "ok"}` | 不要 |
|
|
177
|
+
|
|
178
|
+
### 認証エンドポイント
|
|
179
|
+
|
|
180
|
+
| メソッド | エンドポイント | 説明 | リクエスト | レスポンス | 認証 |
|
|
181
|
+
|---------|---------------|------|-----------|-----------|------|
|
|
182
|
+
| POST | `/api/auth/login` | ユーザーログイン | `{email, password}` | `{token, user}` | 不要 |
|
|
183
|
+
| POST | `/api/auth/logout` | ログアウト | - | `{success: true}` | 必要 |
|
|
184
|
+
| GET | `/api/auth/session` | セッション確認 | - | `{user}` | 必要 |
|
|
185
|
+
|
|
186
|
+
### 投稿エンドポイント
|
|
187
|
+
|
|
188
|
+
| メソッド | エンドポイント | 説明 | リクエスト | レスポンス | 認証 |
|
|
189
|
+
|---------|---------------|------|-----------|-----------|------|
|
|
190
|
+
| GET | `/api/posts` | 投稿一覧取得 | `?page=1&limit=10` | `{posts[], total}` | オプション |
|
|
191
|
+
| POST | `/api/posts` | 投稿作成 | `{title, content, status?}` | `{post}` | 必要 |
|
|
192
|
+
| GET | `/api/posts/:id` | 投稿詳細取得 | - | `{post}` | オプション |
|
|
193
|
+
| PUT | `/api/posts/:id` | 投稿更新 | `{title?, content?, status?}` | `{post}` | 必要 |
|
|
194
|
+
| DELETE | `/api/posts/:id` | 投稿削除 | - | `{success: true}` | 必要 |
|
|
195
|
+
|
|
196
|
+
**ページネーション設計**:
|
|
197
|
+
- `page`: ページ番号(デフォルト: 1)
|
|
198
|
+
- `limit`: 1ページあたりの件数(デフォルト: 10、最大: 100)
|
|
199
|
+
- レスポンス: `{ posts: Post[], total: number }` - totalは全件数
|
|
200
|
+
|
|
201
|
+
**認証制御**:
|
|
202
|
+
- 投稿一覧・詳細: 認証不要(公開投稿のみ取得)
|
|
203
|
+
- 投稿作成・更新・削除: 認証必須
|
|
204
|
+
- 更新・削除: 投稿の所有者チェック(`post.isOwnedBy(userId)`)
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## 3. Admin APIエンドポイント一覧
|
|
209
|
+
|
|
210
|
+
### ヘルスチェック
|
|
211
|
+
|
|
212
|
+
| メソッド | エンドポイント | 説明 | リクエスト | レスポンス | 認証 |
|
|
213
|
+
|---------|---------------|------|-----------|-----------|------|
|
|
214
|
+
| GET | `/api/health` | システム稼働確認 | - | `{status: "ok"}` | 不要 |
|
|
215
|
+
|
|
216
|
+
### ユーザー管理
|
|
217
|
+
|
|
218
|
+
| メソッド | エンドポイント | 説明 | リクエスト | レスポンス | 認証 |
|
|
219
|
+
|---------|---------------|------|-----------|-----------|------|
|
|
220
|
+
| GET | `/api/admin/users` | ユーザー一覧取得 | `?page=1&limit=20` | `{users[], total}` | 管理者 |
|
|
221
|
+
| GET | `/api/admin/users/:id` | ユーザー詳細取得 | - | `{user}` | 管理者 |
|
|
222
|
+
| PUT | `/api/admin/users/:id` | ユーザー情報更新 | `{name?, email?}` | `{user}` | 管理者 |
|
|
223
|
+
| DELETE | `/api/admin/users/:id` | ユーザー削除 | - | `{success: true}` | 管理者 |
|
|
224
|
+
|
|
225
|
+
### 投稿管理
|
|
226
|
+
|
|
227
|
+
| メソッド | エンドポイント | 説明 | リクエスト | レスポンス | 認証 |
|
|
228
|
+
|---------|---------------|------|-----------|-----------|------|
|
|
229
|
+
| GET | `/api/admin/posts` | 全投稿一覧取得 | `?status=all&page=1` | `{posts[], total}` | 管理者 |
|
|
230
|
+
| PUT | `/api/admin/posts/:id/status` | 投稿ステータス変更 | `{status}` | `{post}` | 管理者 |
|
|
231
|
+
|
|
232
|
+
### 分析
|
|
233
|
+
|
|
234
|
+
| メソッド | エンドポイント | 説明 | リクエスト | レスポンス | 認証 |
|
|
235
|
+
|---------|---------------|------|-----------|-----------|------|
|
|
236
|
+
| GET | `/api/admin/analytics` | システム統計取得 | `?from=&to=` | `{stats}` | 管理者 |
|
|
237
|
+
|
|
238
|
+
**管理者認証**:
|
|
239
|
+
- すべてのAdmin APIは、管理者権限(`role='admin'`)のチェックが必要
|
|
240
|
+
- ミドルウェアで JWT検証 + role チェックを実施
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## 4. Cron Worker CLIコマンド
|
|
245
|
+
|
|
246
|
+
cron-workerは、HTTP APIではなく**CLIコマンド**として実装されます。
|
|
247
|
+
|
|
248
|
+
Railway CronまたはVercel Cronなどの外部スケジューラーから直接実行されます。
|
|
249
|
+
|
|
250
|
+
### コマンド一覧
|
|
251
|
+
|
|
252
|
+
| コマンド | 説明 | 実行方法 | 実装ファイル |
|
|
253
|
+
|---------|------|---------|------------|
|
|
254
|
+
| `pnpm job:cleanup` | 期限切れセッションのクリーンアップ | `tsx src/jobs/cleanup.ts` | `apps/cron-worker/src/jobs/cleanup.ts` |
|
|
255
|
+
| `pnpm job:email-digest` | メールダイジェスト送信 | `tsx src/jobs/email-digest.ts` | `apps/cron-worker/src/jobs/email-digest.ts` |
|
|
256
|
+
| `pnpm job:health-check` | ジョブシステムの稼働確認 | `tsx src/jobs/health-check.ts` | `apps/cron-worker/src/jobs/health-check.ts` |
|
|
257
|
+
|
|
258
|
+
### 実装方針
|
|
259
|
+
|
|
260
|
+
- **tsxによるTypeScript直接実行**: ビルド不要で実行可能
|
|
261
|
+
- **Repositoryパターン使用**: `@repo/server-core`の Repository と UseCase を活用
|
|
262
|
+
- **JobCoordinator機構**: 重複実行防止とエラーハンドリング
|
|
263
|
+
- **ログ出力**: すべてのジョブで実行結果をログ出力
|
|
264
|
+
- **終了コード**: 成功時は0、失敗時は非0を返却
|
|
265
|
+
|
|
266
|
+
### package.json設定例
|
|
267
|
+
|
|
268
|
+
```json
|
|
269
|
+
{
|
|
270
|
+
"scripts": {
|
|
271
|
+
"job:cleanup": "dotenv -e ../../.env -e .env.local -- tsx src/jobs/cleanup.ts",
|
|
272
|
+
"job:email-digest": "dotenv -e ../../.env -e .env.local -- tsx src/jobs/email-digest.ts",
|
|
273
|
+
"job:health-check": "dotenv -e ../../.env -e .env.local -- tsx src/jobs/health-check.ts"
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## 5. バリデーション戦略
|
|
281
|
+
|
|
282
|
+
### Zodスキーマ定義
|
|
283
|
+
|
|
284
|
+
すべてのリクエストボディとレスポンスは、Zodスキーマで定義します。
|
|
285
|
+
|
|
286
|
+
**配置場所**: `packages/server-core/src/domain/validators/`
|
|
287
|
+
|
|
288
|
+
**スキーマ例**:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
// packages/server-core/src/domain/validators/post.ts
|
|
292
|
+
import { z } from 'zod'
|
|
293
|
+
|
|
294
|
+
export const createPostSchema = z.object({
|
|
295
|
+
title: z.string().min(1).max(200),
|
|
296
|
+
content: z.string().min(1),
|
|
297
|
+
status: z.enum(['draft', 'published']).default('draft'),
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
export const updatePostSchema = z.object({
|
|
301
|
+
title: z.string().min(1).max(200).optional(),
|
|
302
|
+
content: z.string().min(1).optional(),
|
|
303
|
+
status: z.enum(['draft', 'published', 'archived']).optional(),
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
export type CreatePostInput = z.infer<typeof createPostSchema>
|
|
307
|
+
export type UpdatePostInput = z.infer<typeof updatePostSchema>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### zValidatorの使用
|
|
311
|
+
|
|
312
|
+
**リクエストボディのバリデーション**:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import { zValidator } from '@hono/zod-validator'
|
|
316
|
+
import { createPostSchema } from '@repo/server-core/domain/validators/post'
|
|
317
|
+
|
|
318
|
+
app.post('/posts', zValidator('json', createPostSchema), async (c) => {
|
|
319
|
+
const data = c.req.valid('json') // バリデート済みデータを型安全に取得
|
|
320
|
+
// data は CreatePostInput 型として推論される
|
|
321
|
+
})
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**クエリパラメータのバリデーション**:
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
const querySchema = z.object({
|
|
328
|
+
page: z.string().transform(Number).default('1'),
|
|
329
|
+
limit: z.string().transform(Number).default('10'),
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
app.get('/posts', zValidator('query', querySchema), async (c) => {
|
|
333
|
+
const { page, limit } = c.req.valid('query')
|
|
334
|
+
})
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### 型の自動生成
|
|
338
|
+
|
|
339
|
+
Zodスキーマから型を自動生成することで、型の重複を防ぎます:
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
export type CreatePostInput = z.infer<typeof createPostSchema>
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
この型は、フロントエンドとバックエンドで共有されます。
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## 6. エラーハンドリング
|
|
350
|
+
|
|
351
|
+
### ApplicationError階層
|
|
352
|
+
|
|
353
|
+
`@repo/server-core/utils/errors.ts`で定義されたエラークラス階層を使用します。
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
// ApplicationError 基底クラス
|
|
357
|
+
class ApplicationError extends Error {
|
|
358
|
+
constructor(
|
|
359
|
+
public code: string,
|
|
360
|
+
message: string,
|
|
361
|
+
public statusCode: number = 500
|
|
362
|
+
) {
|
|
363
|
+
super(message)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// 派生エラークラス
|
|
368
|
+
class ValidationError extends ApplicationError {
|
|
369
|
+
constructor(message: string) {
|
|
370
|
+
super('VALIDATION_ERROR', message, 400)
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
class NotFoundError extends ApplicationError {
|
|
375
|
+
constructor(message: string) {
|
|
376
|
+
super('NOT_FOUND', message, 404)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
class UnauthorizedError extends ApplicationError {
|
|
381
|
+
constructor(message: string) {
|
|
382
|
+
super('UNAUTHORIZED', message, 401)
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
class ForbiddenError extends ApplicationError {
|
|
387
|
+
constructor(message: string) {
|
|
388
|
+
super('FORBIDDEN', message, 403)
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
class DatabaseError extends ApplicationError {
|
|
393
|
+
constructor(message: string) {
|
|
394
|
+
super('DATABASE_ERROR', message, 500)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Result型からApiResponseへの変換
|
|
400
|
+
|
|
401
|
+
UseCaseは`Result<T, E>`型を返却しますが、APIレスポンスは`ApiResponse`形式に変換します。
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
// ApiResponse型定義
|
|
405
|
+
type ApiResponse<T> = {
|
|
406
|
+
data?: T
|
|
407
|
+
error?: {
|
|
408
|
+
code: string
|
|
409
|
+
message: string
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Result → ApiResponse 変換パターン
|
|
414
|
+
app.post('/posts', zValidator('json', createPostSchema), async (c) => {
|
|
415
|
+
const data = c.req.valid('json')
|
|
416
|
+
const result = await postUseCases.create(data)
|
|
417
|
+
|
|
418
|
+
if (!result.isSuccess) {
|
|
419
|
+
const error = result.error
|
|
420
|
+
return c.json(
|
|
421
|
+
{ error: { code: error.code, message: error.message } },
|
|
422
|
+
error.statusCode
|
|
423
|
+
)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return c.json({ data: result.value }, 201)
|
|
427
|
+
})
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### HTTPステータスコードマッピング
|
|
431
|
+
|
|
432
|
+
| エラー種別 | HTTPステータス | 説明 |
|
|
433
|
+
|----------|--------------|------|
|
|
434
|
+
| ValidationError | 400 | リクエストデータが不正 |
|
|
435
|
+
| UnauthorizedError | 401 | 認証が必要 |
|
|
436
|
+
| ForbiddenError | 403 | 権限不足 |
|
|
437
|
+
| NotFoundError | 404 | リソースが存在しない |
|
|
438
|
+
| DatabaseError | 500 | データベースエラー |
|
|
439
|
+
| ApplicationError | 500 | その他のサーバーエラー |
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## 7. フロントエンド統合パターン
|
|
444
|
+
|
|
445
|
+
フロントエンドからAPIを呼び出す方法には、**Server Actions**と**Hono Client + Tanstack Query**の2つのパターンがあります。
|
|
446
|
+
|
|
447
|
+
### パターン比較
|
|
448
|
+
|
|
449
|
+
| 観点 | Server Actions | Hono Client + Tanstack Query |
|
|
450
|
+
|------|---------------|------------------------------|
|
|
451
|
+
| **複雑さ** | シンプル | やや複雑 |
|
|
452
|
+
| **キャッシュ** | Next.jsのrevalidate | Tanstack Queryの高度なキャッシュ |
|
|
453
|
+
| **楽観的更新** | 手動実装 | 組み込みサポート |
|
|
454
|
+
| **ローディング状態** | useFormStatus/useTransition | isPending/isLoading |
|
|
455
|
+
| **エラーハンドリング** | try-catch | onError コールバック |
|
|
456
|
+
| **リアルタイム更新** | 不向き | refetchInterval等で対応可 |
|
|
457
|
+
| **型安全性** | 高い | 高い(RPC型推論) |
|
|
458
|
+
|
|
459
|
+
### 使い分け基準
|
|
460
|
+
|
|
461
|
+
#### ✅ Server Actionsを使う場合
|
|
462
|
+
|
|
463
|
+
- **シンプルなフォーム送信**(お問い合わせ、ログイン等)
|
|
464
|
+
- **単発のミューテーション**(いいね、フォロー等)
|
|
465
|
+
- **SEOが重要なページ**でのデータ更新
|
|
466
|
+
- **Progressive Enhancement**が必要な場合(JS無効でも動作)
|
|
467
|
+
- **キャッシュ管理が単純**な場合
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
// app/actions/post.ts
|
|
471
|
+
'use server'
|
|
472
|
+
|
|
473
|
+
import { revalidatePath } from 'next/cache'
|
|
474
|
+
import { postUseCases } from '@/application/use-cases/PostUseCases'
|
|
475
|
+
import { createPostSchema } from '@repo/server-core/domain/validators/post'
|
|
476
|
+
|
|
477
|
+
export async function createPost(formData: FormData) {
|
|
478
|
+
const rawData = {
|
|
479
|
+
title: formData.get('title'),
|
|
480
|
+
content: formData.get('content'),
|
|
481
|
+
status: formData.get('status') || 'draft',
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// バリデーション
|
|
485
|
+
const parsed = createPostSchema.safeParse(rawData)
|
|
486
|
+
if (!parsed.success) {
|
|
487
|
+
return { error: parsed.error.flatten().fieldErrors }
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// UseCase呼び出し
|
|
491
|
+
const result = await postUseCases.create(parsed.data)
|
|
492
|
+
|
|
493
|
+
if (!result.isSuccess) {
|
|
494
|
+
return { error: { _form: [result.error.message] } }
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// キャッシュ無効化
|
|
498
|
+
revalidatePath('/posts')
|
|
499
|
+
|
|
500
|
+
return { success: true, post: result.value }
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
// components/features/posts/PostCreateForm.tsx
|
|
506
|
+
'use client'
|
|
507
|
+
|
|
508
|
+
import { useActionState } from 'react'
|
|
509
|
+
import { createPost } from '@/app/actions/post'
|
|
510
|
+
|
|
511
|
+
export function PostCreateForm() {
|
|
512
|
+
const [state, formAction, isPending] = useActionState(createPost, null)
|
|
513
|
+
|
|
514
|
+
return (
|
|
515
|
+
<form action={formAction}>
|
|
516
|
+
<input name="title" required />
|
|
517
|
+
{state?.error?.title && <p className="error">{state.error.title}</p>}
|
|
518
|
+
|
|
519
|
+
<textarea name="content" required />
|
|
520
|
+
{state?.error?.content && <p className="error">{state.error.content}</p>}
|
|
521
|
+
|
|
522
|
+
<button type="submit" disabled={isPending}>
|
|
523
|
+
{isPending ? '作成中...' : '投稿を作成'}
|
|
524
|
+
</button>
|
|
525
|
+
|
|
526
|
+
{state?.error?._form && <p className="error">{state.error._form}</p>}
|
|
527
|
+
</form>
|
|
528
|
+
)
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
#### ✅ Hono Client + Tanstack Queryを使う場合
|
|
533
|
+
|
|
534
|
+
- **複数コンポーネントでデータ共有**が必要
|
|
535
|
+
- **高度なキャッシュ管理**(staleTime、cacheTime、条件付きrefetch)
|
|
536
|
+
- **楽観的更新**が必要(即座にUIに反映)
|
|
537
|
+
- **ポーリング・リアルタイム更新**が必要
|
|
538
|
+
- **複雑なデータフェッチ**(依存クエリ、並列クエリ)
|
|
539
|
+
- **ページネーション・無限スクロール**
|
|
540
|
+
|
|
541
|
+
詳細な実装方法は **[フロントエンド開発ガイド](frontend-development.md)** を参照してください:
|
|
542
|
+
- セクション3: Hono Client統合
|
|
543
|
+
- セクション5: Tanstack Query
|
|
544
|
+
- セクション6: React Hook Form
|
|
545
|
+
|
|
546
|
+
### 推奨パターン早見表
|
|
547
|
+
|
|
548
|
+
| ユースケース | 推奨パターン |
|
|
549
|
+
|------------|-------------|
|
|
550
|
+
| ログインフォーム | Server Actions |
|
|
551
|
+
| お問い合わせフォーム | Server Actions |
|
|
552
|
+
| いいねボタン | Server Actions |
|
|
553
|
+
| 投稿一覧(ページネーション付き) | Hono Client + Tanstack Query |
|
|
554
|
+
| コメント機能(リアルタイム更新) | Hono Client + Tanstack Query |
|
|
555
|
+
| 管理画面のCRUD | Hono Client + Tanstack Query |
|
|
556
|
+
| 検索機能(デバウンス付き) | Hono Client + Tanstack Query |
|
|
557
|
+
| ファイルアップロード | Server Actions |
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## 8. 認証とミドルウェア
|
|
562
|
+
|
|
563
|
+
### 認証ミドルウェア
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
// apps/web/server/middleware/auth.ts
|
|
567
|
+
import { createMiddleware } from 'hono/factory'
|
|
568
|
+
import { verify } from 'jsonwebtoken'
|
|
569
|
+
|
|
570
|
+
export const authMiddleware = createMiddleware(async (c, next) => {
|
|
571
|
+
const token = c.req.header('Authorization')?.replace('Bearer ', '')
|
|
572
|
+
|
|
573
|
+
if (!token) {
|
|
574
|
+
return c.json({ error: 'Unauthorized' }, 401)
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
try {
|
|
578
|
+
const payload = verify(token, process.env.JWT_SECRET!)
|
|
579
|
+
c.set('user', payload) // コンテキストにユーザー情報を設定
|
|
580
|
+
await next()
|
|
581
|
+
} catch (error) {
|
|
582
|
+
return c.json({ error: 'Invalid token' }, 401)
|
|
583
|
+
}
|
|
584
|
+
})
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
### 管理者権限チェックミドルウェア
|
|
588
|
+
|
|
589
|
+
```typescript
|
|
590
|
+
// apps/admin/server/middleware/admin.ts
|
|
591
|
+
import { createMiddleware } from 'hono/factory'
|
|
592
|
+
import { authMiddleware } from './auth'
|
|
593
|
+
|
|
594
|
+
export const adminMiddleware = createMiddleware(async (c, next) => {
|
|
595
|
+
await authMiddleware(c, async () => {
|
|
596
|
+
const user = c.get('user')
|
|
597
|
+
|
|
598
|
+
if (user.role !== 'admin') {
|
|
599
|
+
return c.json({ error: 'Forbidden' }, 403)
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
await next()
|
|
603
|
+
})
|
|
604
|
+
})
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### ミドルウェアの適用
|
|
608
|
+
|
|
609
|
+
```typescript
|
|
610
|
+
import { Hono } from 'hono'
|
|
611
|
+
import { authMiddleware } from '@/server/middleware/auth'
|
|
612
|
+
|
|
613
|
+
const app = new Hono()
|
|
614
|
+
|
|
615
|
+
// 認証不要なルート
|
|
616
|
+
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
617
|
+
|
|
618
|
+
// 認証が必要なルート
|
|
619
|
+
app.use('/posts/*', authMiddleware)
|
|
620
|
+
app.post('/posts', async (c) => {
|
|
621
|
+
const user = c.get('user') // ミドルウェアで設定されたユーザー情報
|
|
622
|
+
// ...
|
|
623
|
+
})
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
**⚠️ 重要**: サブルート内で`.use()`を使用するとHono RPC型推論が壊れます。
|
|
627
|
+
詳細は[ミドルウェアと型推論の注意点](#ミドルウェアと型推論の注意点)を参照してください。
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
## 9. レスポンス設計
|
|
632
|
+
|
|
633
|
+
### 統一レスポンス形式
|
|
634
|
+
|
|
635
|
+
すべてのAPIレスポンスは、以下の形式に従います:
|
|
636
|
+
|
|
637
|
+
**成功時**:
|
|
638
|
+
```typescript
|
|
639
|
+
{
|
|
640
|
+
data: T // 実際のデータ
|
|
641
|
+
}
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
**エラー時**:
|
|
645
|
+
```typescript
|
|
646
|
+
{
|
|
647
|
+
error: {
|
|
648
|
+
code: string // エラーコード(例: "VALIDATION_ERROR")
|
|
649
|
+
message: string // エラーメッセージ
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### ページネーションレスポンス
|
|
655
|
+
|
|
656
|
+
```typescript
|
|
657
|
+
{
|
|
658
|
+
data: {
|
|
659
|
+
items: T[] // データ配列
|
|
660
|
+
total: number // 全件数
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
---
|
|
666
|
+
|
|
667
|
+
## 10. 環境変数管理
|
|
668
|
+
|
|
669
|
+
### dotenv-cliの使用
|
|
670
|
+
|
|
671
|
+
環境変数は、dotenv-cliを使用して階層的にロードされます。
|
|
672
|
+
|
|
673
|
+
**ロード順序**(後勝ち):
|
|
674
|
+
1. `root/.env` - モノレポ全体の共通設定
|
|
675
|
+
2. `root/.env.local` - ローカル環境でのオーバーライド
|
|
676
|
+
3. `apps/*/.env.local` - アプリケーション固有のオーバーライド
|
|
677
|
+
|
|
678
|
+
**package.jsonでの設定**:
|
|
679
|
+
|
|
680
|
+
```json
|
|
681
|
+
{
|
|
682
|
+
"scripts": {
|
|
683
|
+
"dev": "dotenv -e ../../.env -e ../../.env.local -e .env.local -- next dev",
|
|
684
|
+
"build": "dotenv -e ../../.env -e .env.local -- next build"
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
### Worktree対応の動的ポート割り当て
|
|
690
|
+
|
|
691
|
+
Worktreeごとに異なるポートを使用するため、MD5ハッシュベースの動的ポート割り当てを実装します。
|
|
692
|
+
|
|
693
|
+
詳細は`deployment.md`を参照してください。
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
## 11. 実装例
|
|
698
|
+
|
|
699
|
+
### 完全なPOST /api/posts実装
|
|
700
|
+
|
|
701
|
+
**ルート定義**:
|
|
702
|
+
|
|
703
|
+
```typescript
|
|
704
|
+
// apps/web/server/routes/postRoutes.ts
|
|
705
|
+
import { Hono } from 'hono'
|
|
706
|
+
import { zValidator } from '@hono/zod-validator'
|
|
707
|
+
import { createPostSchema } from '@repo/server-core/domain/validators/post'
|
|
708
|
+
import { postUseCases } from '@repo/server-core/application/use-cases/postUseCases'
|
|
709
|
+
import { authMiddleware } from '@/server/middleware/auth'
|
|
710
|
+
|
|
711
|
+
const app = new Hono()
|
|
712
|
+
|
|
713
|
+
app.post(
|
|
714
|
+
'/posts',
|
|
715
|
+
authMiddleware, // 認証ミドルウェア
|
|
716
|
+
zValidator('json', createPostSchema), // バリデーション
|
|
717
|
+
async (c) => {
|
|
718
|
+
// 1. バリデート済みデータの取得
|
|
719
|
+
const data = c.req.valid('json')
|
|
720
|
+
const user = c.get('user') // 認証ユーザー情報
|
|
721
|
+
|
|
722
|
+
// 2. UseCaseの呼び出し
|
|
723
|
+
const result = await postUseCases.create({
|
|
724
|
+
...data,
|
|
725
|
+
userId: user.id,
|
|
726
|
+
})
|
|
727
|
+
|
|
728
|
+
// 3. Result型の処理
|
|
729
|
+
if (!result.isSuccess) {
|
|
730
|
+
const error = result.error
|
|
731
|
+
return c.json(
|
|
732
|
+
{ error: { code: error.code, message: error.message } },
|
|
733
|
+
error.statusCode
|
|
734
|
+
)
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// 4. 成功レスポンス
|
|
738
|
+
return c.json({ data: result.value }, 201)
|
|
739
|
+
}
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
export default app
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
**エントリーポイント**:
|
|
746
|
+
|
|
747
|
+
```typescript
|
|
748
|
+
// apps/web/app/api/[[...route]]/route.ts
|
|
749
|
+
import { Hono } from 'hono'
|
|
750
|
+
import { handle } from 'hono/vercel'
|
|
751
|
+
import postRoutes from '@/server/routes/postRoutes'
|
|
752
|
+
|
|
753
|
+
const app = new Hono().basePath('/api')
|
|
754
|
+
|
|
755
|
+
app.route('/', postRoutes)
|
|
756
|
+
|
|
757
|
+
export const GET = handle(app)
|
|
758
|
+
export const POST = handle(app)
|
|
759
|
+
export const PUT = handle(app)
|
|
760
|
+
export const DELETE = handle(app)
|
|
761
|
+
|
|
762
|
+
export type AppType = typeof app
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
**フロントエンドでの使用**:
|
|
766
|
+
|
|
767
|
+
フロントエンドからこのAPIを呼び出す実装例は **[フロントエンド開発ガイド](frontend-development.md)** を参照してください:
|
|
768
|
+
|
|
769
|
+
- **Server Actionsパターン**: セクション7「フロントエンド統合パターン」(本ドキュメント)
|
|
770
|
+
- **Hono Client + Tanstack Queryパターン**: [フロントエンド開発ガイド](frontend-development.md) セクション11
|
|
771
|
+
|
|
772
|
+
---
|
|
773
|
+
|
|
774
|
+
## まとめ
|
|
775
|
+
|
|
776
|
+
このAPI開発ガイドに従うことで、以下を実現できます:
|
|
777
|
+
|
|
778
|
+
1. **型安全性**: Hono + Zod + TypeScriptによるエンドツーエンドの型推論
|
|
779
|
+
2. **統一性**: モノレポ全体で統一されたAPI設計パターン
|
|
780
|
+
3. **保守性**: Result型パターンとエラーハンドリングによる明確な制御フロー
|
|
781
|
+
4. **柔軟性**: Server ActionsとHono Client + Tanstack Queryの使い分け
|
|
782
|
+
|
|
783
|
+
すべてのAPI実装は、このガイドラインに従って実装してください。
|