mit einem Klick
api
APIのプロトコル、定義の書き方、実装の書き方、クライアントでの利用の仕方。
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Menü
APIのプロトコル、定義の書き方、実装の書き方、クライアントでの利用の仕方。
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Basierend auf der SOC-Berufsklassifikation
Cloudflare D1, Drizzle のマイグレーションとローカル運用に関するコマンド集。スキーマ変更やローカル検証時に読みます。
ESLint と TypeScript 型チェックの実行コマンド。コード品質チェックや自動修正に使います。
Claude Code Routinesなどで一定の日時に起動して作業するためのスキル。おもに5時間の利用制限が解除されそうなタイミングで呼ばれる。作業内容は、 1. テストが落ちていないか確認 - 2. HIGH PRIORITY Issueの解消 - 3. PRメンテナンス - 4. Issue消化。暇ならコードレビュー。複数のIssueに対する並行作業禁止。
本番ビルドと Cloudflare Workers へのデプロイ手順。デプロイ前の注意点(wrangler.jsonc の設定など)を含みます。
ローカル開発の起動・停止、開発サーバーの使用方法。新しい開発者がローカルで作業を始めるときに役立ちます。
IDに利用しているEAID-Xはミリ秒の日付情報を持っています。データベースのプライマリキーに使います。EAID-Xを変換することでデータの作成時刻(createdAt)を得ることができます。
| name | api |
| description | APIのプロトコル、定義の書き方、実装の書き方、クライアントでの利用の仕方。 |
| tags | ["api","typescript","hono","schema","openapi"] |
エンドポイントは/api/以下。
/api/meta/api/an/endpoint/:userIdpackages/app/src/shared/api.schemas.ts - 共有エラーレスポンススキーマpackages/app/src/shared/api-errors.ts - APIエラーコードと既定メッセージpackages/app/src/shared/api/index.ts - APIスキーマ定義ファイルのインデックスpackages/app/src/shared/api/*.ts - APIスキーマ定義ファイルpackages/app/src/worker/api/*.ts - API実装ファイル ハンドラshared/api.schemas.ts全エラーレスポンスで使う共通スキーマ:
import * as v from 'valibot';
import { apiErrorMessages } from './api-errors.js';
export const ErrorResponse = v.pipe(
v.object({
error: v.picklist(Object.keys(apiErrorMessages) as [keyof typeof apiErrorMessages, ...(keyof typeof apiErrorMessages)[]]),
message: v.string(),
}),
v.metadata({ ref: 'ErrorResponse' }),
);
エラーレスポンスは次の形にする:
{
"error": "BUCKET_NOT_FOUND",
"message": "Bucket not found"
}
error はフロントエンドなど機械が分岐するための stable な SNAKE_CASE codemessage は人間向け表示テキストpackages/app/src/shared/api-errors.ts の apiErrorMessages に追加するmessage 側を変えるshared/api/*.tsimport * as v from 'valibot';
import type { ApiEndpointDefinitionRecord } from '../api.types.js';
import { ErrorResponse } from '../api.schemas.js';
// OpenAPI ref ... 複数のAPIでスキーマを共有する場合
const UserResponse = v.pipe(
v.object({
id: v.string(),
username: v.string(),
isAdmin: v.boolean(),
}),
v.metadata({ ref: 'User' }),
);
export const exampleApiDef = {
'/api/users/show': {
summary: 'Get account info from user id',
tags: ['users'],
req: v.object({
userId: v.string(),
}),
// 【重要】全ての応答コードに description と content + vSchema を設定すること。
// describeResponse の型推論は vSchema から T を組み立てるため、
// content が欠けているエントリがあると T の推論が壊れ連鎖型エラーになる。
// エラー応答は onError が { error: ApiErrorCode, message: string } を返すので ErrorResponse を使う。
res: {
200: { description: 'Success', content: { 'application/json': { vSchema: UserResponse } } },
404: { description: 'Not Found', content: { 'application/json': { vSchema: ErrorResponse } } },
401: { description: 'Unauthorized', content: { 'application/json': { vSchema: ErrorResponse } } },
},
},
} as const satisfies ApiEndpointDefinitionRecord;
shared/api/index.ts新しいAPIファイルを作成する場合は、index.ts の apiDef にdefオブジェクトを追加してください。
worker/api/*.tsimport { Hono } from 'hono';
import { describeResponse, describeRoute, validator } from 'hono-openapi';
import { eq } from 'drizzle-orm';
import { authMiddleware } from '../middleware/auth';
import { getDb } from '../utils/db';
import { users } from '../scheme/index';
import { apiError } from '../utils/api-error';
import { apiDef, getResponseDefWithAuth, type JsonCtx } from '../../shared/api';
import { omitResAndReq } from '../utils/omit';
const app = new Hono<{ Bindings: Env }>();
app.use(authMiddleware);
// 【重要】describeResponse のハンドラには必ず JsonCtx<endpoint, Env> を明示する。
// describeResponse は前段の validator から Env・バリデーション入力型を引き継がないため、
// 注釈がないと c.env や c.req.valid('json') の型が壊れる。
// req を使わないハンドラも統一して JsonCtx を付ける。
app.post(
'/show',
describeRoute(omitResAndReq(apiDef['/api/users/show'])),
validator('json', apiDef['/api/users/show'].req),
describeResponse(async (c: JsonCtx<'/api/users/show', Env>) => {
const db = getDb(c.env);
const body = c.req.valid('json');
const user = await db
.select()
.from(users)
.where(eq(users.id, body.userId))
.get();
if (!user) {
throw apiError(404, 'USER_NOT_FOUND');
}
// `, 200`は必須
return c.json({
id: user.id,
username: user.username,
isAdmin: user.isAdmin,
}, 200);
}, getResponseDefWithAuth('/api/users/show')),
// ↑ authMiddleware を使うルートは getResponseDefWithAuth を使う(401/403を自動付与)
);
export const exampleRoutes = app;
APIハンドラでは HTTPException を直接投げず、apiError(status, code, message?) を使う。
throw apiError(404, 'BUCKET_NOT_FOUND');
throw apiError(400, 'INVALID_FILE_PATH', `path must be at most ${MAX_FILE_PATH_LENGTH} characters`);
code は shared/api-errors.ts の apiErrorMessages に存在するキーだけを使うmessage を省略すると apiErrorMessages[code] が使われるerror code は安定させ、詳細だけ message に入れるworker/index.ts の onError が ApiError を { error: code, message } に変換するHono/hono-openapi のジェネリクスが崩れると広範囲に解読困難な型エラーが出る。以下を順番に確認する。
content + vSchema があるか(最頻出)describeResponse は各エントリの vSchema から型パラメータ T を推論する。
description だけのエントリが1つでもあると T の推論が壊れ Handler<Env,...> の不一致エラーが連鎖する。
// ❌ NG: description だけのエントリがあると型エラー
res: {
200: { description: 'Success', content: { 'application/json': { vSchema: v.object({ ok: v.literal(true) }) } } },
400: { description: 'Bad request' }, // ← これがあるだけで全体が壊れる
}
// ✅ OK: 全エントリに content + vSchema
res: {
200: { description: 'Success', content: { 'application/json': { vSchema: v.object({ ok: v.literal(true) }) } } },
400: { description: 'Bad request', content: { 'application/json': { vSchema: ErrorResponse } } },
}
c に JsonCtx<endpoint, Env> が付いているかdescribeResponse は前段 validator の型 (E/I) を引き継がないため、c に注釈がないと c.env が object 、c.req.valid('json') が never になる。
// ❌ NG
describeResponse(async (c) => { ... }, res)
// ✅ OK
describeResponse(async (c: JsonCtx<'/api/endpoint', Env>) => { ... }, res)
getResponseDefWithAuth の 401 競合endpoint の res に 401 が定義されていると authErrorResponses の 401 と交差して description: never になる場合があった(修正済み)。getResponseDefWithAuth は endpoint の res でauth側を上書きする型になっている。
POST JSON body タイプのAPIでは、 apiPost を利用できる。
import { apiPost } from '../utils/api';
失敗時の ApiFailure は data.error がエラーコード、data.message が表示用テキスト。
const result = await apiPost('/api/buckets/delete', { bucketId });
if (!result.ok) {
if (result.data.error === 'BUCKET_NOT_FOUND') {
// stable codeで分岐
}
error.value = result.data.message; // 表示には message を使う
}