テーマ
API
構成方針
- API 本体は Hono で実装する。
- Nuxt の
server/api/[...].tsから Hono アプリをマウントし、SSR と同一 Worker で配信する。 - フロント-サーバ間の型共有は Hono RPC を使う(Hono の型推論を
hono/client側で受ける)。
Nuxt とのマウント
ts
// app/server/api/[...].ts (擬似コード)
import { app } from '~~/server/hono'
export default defineEventHandler((event) => {
return app.fetch(event.node.req as any, {
DB: useDB(event),
KV: useKV(event),
R2: useR2(event),
SECRETS: useSecrets(event),
})
})ルート設計
| Prefix | 用途 |
|---|---|
/api/v1/auth/* | Better Auth ルート |
/api/v1/me | 自分自身の情報 |
/api/v1/tenants/:tenantId/... | テナントスコープのリソース |
/api/v1/customers | 自テナントの顧客(暗黙テナント) |
/api/v1/contracts | 同上 |
/api/v1/schedules | 同上 |
/api/v1/activities | 同上 |
/api/v1/files | 書類 / 画像 |
/api/v1/line/channels / /messages / /menus / /surveys | LINE 関連 |
/api/v1/billing/* | テナント課金(OWNERのみ) |
/api/admin/* | ADMIN 用 |
/api/webhooks/line/:channelId | LINE Webhook |
/api/webhooks/stripe / /api/webhooks/square | 決済 Webhook |
「テナントID をパス必須にするか暗黙にするか」: 暗黙(セッション由来) を基本 とし、ADMIN による横断操作のみ :tenantId を明示する /api/admin/tenants/:tenantId/... を用意。
ミドルウェア順
Auth: Cookie からセッション復元Tenant:user.tenantIdをコンテキストへRoleGuard: ルート単位でrequireRole('OWNER')等Validation: 入力は Zod スキーマ。RPC の型推論にも使うError MW:HTTPExceptionを 4xx/5xx へ整形
入力検証とレスポンス
- 入力:
zValidator('json', schema)/'query'/'param' - レスポンス形:json
// 正常 { "data": { ... } } // エラー { "error": { "code": "FORBIDDEN", "message": "...", "details": {...} } } - HTTPステータスは意味と一致させる。
200 / 201 / 204 / 400 / 401 / 403 / 404 / 409 / 422 / 429 / 500。
エラーコード(暫定)
| コード | 意味 |
|---|---|
UNAUTHENTICATED | 未認証 |
FORBIDDEN | 認可エラー(テナント越境を含む) |
NOT_FOUND | リソース未存在 |
VALIDATION_ERROR | 入力検証エラー |
CONFLICT | 競合(一意制約等) |
RATE_LIMITED | レート制限 |
INTEGRATION_ERROR | 外部API失敗 |
INTERNAL | 想定外 |
Hono RPC クライアント(フロント側)
ts
// app/composables/useApi.ts (擬似コード)
import { hc } from 'hono/client'
import type { AppType } from '~~/server/hono'
export const useApi = () => hc<AppType>('/api/v1')- ルート定義の戻り値型をフロントで再利用 → リクエスト/レスポンスの取り違えがコンパイル時に検出される。
useApi()を Nuxt のuseFetchと組み合わせず、SSR時は Worker 内で直呼び(app.request(...))するパターンも検討。
ページネーションとカーソル
- 大量データは カーソルベース が基本(
?cursor=...&limit=50)。 - 集計や全件取得が必要な箇所は管理画面に隔離。
レート制限
- パブリックエンドポイント / Webhook には KV ベースの簡易レート制限を入れる。
- 一斉配信などの自エンドポイントはアプリ層でジョブ化(Cloudflare Queues検討)。
ロギング
- リクエスト ID(
crypto.randomUUID())を付与し全ログに含める。 - センシティブ情報(メール本文、LINE 本文、PII)はログに残さない or マスク。
未確定
- バージョニング戦略(
v1を破壊的変更でv2