en un clic
api
APIのプロトコル、定義の書き方、実装の書き方、クライアントでの利用の仕方。
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Menu
APIのプロトコル、定義の書き方、実装の書き方、クライアントでの利用の仕方。
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Basé sur la classification professionnelle SOC
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 を使う
}