validation について
AJVを使ってJavaScriptプロジェクトのJSON Schema検証を支援するスキルです。API入力やサービスデータを検証し、スキーマ、生成したTypeScript宣言、ドキュメントを連携させます。
主な機能
- 01AJVによるJSON Schema検証
- 02リクエスト本文・クエリ・パラメーター用ミドルウェア
- 03クエリパラメーターの型変換
- 04TypeScript宣言の生成
- 05スキーマとDB制約の整合
活用シーン
- 01APIリクエストやフォーム送信の検証
- 02データベース書き込み前のデータ確認
- 03サービス間の契約定義
- 04スキーマからTypeScript宣言を生成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ profpowell/ vanilla-breeze --skill validationインストール後、Claude Code で/validationを入力して読み込まれたか確認します。
詳細
JSON Schema 検証スキル
AJV の実行時検証で JSON Schema 定義を使用し、すべての境界でデータを検証します。
使用する場面
- ユーザー入力を受け付ける API エンドポイントを作成するとき
- フォームの送信内容をサーバー側で検証するとき
- データベースに書き込む前にデータの整合性を確保するとき
- サービス間のコントラクトを定義するとき
- スキーマから TypeScript 型を生成するとき
スキーマファイルの場所
スキーマは、次の構成の /schemas/ ディレクトリに配置します。
schemas/
common/ # Shared/reusable schemas
uuid.schema.json
error-response.schema.json
pagination.schema.json
entities/ # Domain entity schemas
user.schema.json # Full entity
user.create.schema.json # Create input (no id/timestamps)
user.update.schema.json # Partial update (all optional)
api/ # API-specific request schemas
login.schema.json
register.schema.json
スキーマの命名規則
| パターン | 例 | 用途 |
|---|---|---|
{entity}.schema.json | user.schema.json | すべてのフィールドを含む完全なエンティティ |
{entity}.create.schema.json | user.create.schema.json | 作成時の入力(id とタイムスタンプを含まない) |
{entity}.update.schema.json | user.update.schema.json | 部分更新(すべてのフィールドが任意) |
{context}.schema.json | login.schema.json | コンテキスト固有のスキーマ |
スキーマの作成
基本的なスキーマテンプレート
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "entities/user.create",
"title": "Create User",
"description": "Schema for creating a new user",
"type": "object",
"required": ["email", "password"],
"properties": {
"email": {
"type": "string",
"format": "email",
"maxLength": 254,
"description": "User's email address"
},
"password": {
"type": "string",
"minLength": 8,
"maxLength": 128,
"description": "User's password (8-128 characters)"
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100,
"description": "User's display name"
}
},
"additionalProperties": false
}
主な属性
| 属性 | 用途 |
|---|---|
$id | 参照用の一意な識別子(例:"entities/user.create") |
required | 必須フィールド名の配列 |
additionalProperties: false | 不明なフィールドを拒否します(セキュリティ) |
minProperties: 1 | 更新スキーマ向け。少なくとも 1 つのフィールドを必須にします |
よく使われる検証キーワード
文字列の検証:
{
"type": "string",
"minLength": 1,
"maxLength": 255,
"pattern": "^[a-z0-9-]+$",
"format": "email"
}
数値の検証:
{
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
列挙値の検証:
{
"type": "string",
"enum": ["draft", "active", "archived"],
"default": "draft"
}
null を許容するフィールド:
{
"type": ["string", "null"],
"maxLength": 2000
}
利用可能なフォーマット
AJV は ajv-formats を使用して、次のフォーマットをサポートします。
email- メールアドレスuri- 完全な URIuuid- UUID v4date- ISO 日付(YYYY-MM-DD)date-time- ISO 日時time- ISO 時刻ipv4、ipv6- IP アドレスhostname- ホスト名
カスタムフォーマット(validator.js で定義):
phone- E.164 電話番号形式(+1234567890)slug- URL で安全に使用できる識別子(小文字、ハイフン)
検証ミドルウェアの使用
インポートして適用する
import { validateBody, validateQuery, validateParams } from './middleware/validate.js';
// Validate request body
app.post('/api/users',
validateBody('entities/user.create'),
createUser
);
// Validate query parameters
app.get('/api/items',
validateQuery('api/list-items'),
listItems
);
// Validate path parameters
app.get('/api/users/:id',
validateParams('common/uuid-param'),
getUser
);
// Combined validation
app.patch('/api/users/:id',
validateParams('common/uuid-param'),
validateBody('entities/user.update'),
updateUser
);
エラーレスポンス形式
検証に失敗した場合、次の内容を返します。
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"path": "/email",
"message": "Invalid email format",
"keyword": "format"
},
{
"path": "/password",
"message": "Must be at least 8 characters",
"keyword": "minLength"
}
]
}
}
ステータスコード:
422- リクエストボディの検証に失敗しました400- クエリまたはパラメーターの検証に失敗しました
サービスでの検証
ミドルウェア以外で検証する場合(例:データベースへの書き込み前)は、次のようにします。
import { validate } from '../api/middleware/validate.js';
async function createUser(data) {
// Validate before database insert (defense in depth)
validate(data, 'entities/user.create');
// Proceed with insert...
const result = await query(userQueries.create, [data.email, data.name]);
return result.rows[0];
}
クエリパラメーターの型変換
クエリ文字列は常に文字列です。ミドルウェアは次の型変換を自動で行います。
| スキーマの型 | 入力 | 結果 |
|---|---|---|
integer | "20" | 20 |
boolean | "true" | true |
array | "a,b,c" | ["a", "b", "c"] |
クエリスキーマの例:
{
"$id": "api/list-items",
"type": "object",
"properties": {
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0
},
"status": {
"type": "string",
"enum": ["draft", "active", "archived"]
}
},
"additionalProperties": false
}
TypeScript 型の生成
JSDoc の型チェック用に、スキーマから .d.ts ファイルを生成します。
npm run generate:types
仕組み
このスクリプトは json-schema-to-typescript を使用して、JSON Schema ファイルを TypeScript 宣言ファイルに変換します。
/schemas/内のすべての.schema.jsonファイルを読み込みますsrc/types/generated/に対応する.d.tsファイルを生成します- 型は、型チェックのために JSDoc コメント内でインポートできます
生成される構成
src/types/generated/
common/
uuid.d.ts
error-response.d.ts
pagination.d.ts
entities/
user.d.ts
user.create.d.ts
user.update.d.ts
api/
login.d.ts
register.d.ts
生成された型の使用
JSDoc コメント内で型をインポートします。
/**
* @typedef {import('./types/generated/entities/user.create').UserCreate} CreateUserInput
* @typedef {import('./types/generated/entities/user').User} User
*/
/**
* Create a new user
* @param {CreateUserInput} data - User creation data
* @returns {Promise<User>} Created user
*/
async function createUser(data) {
validate(data, 'entities/user.create');
// data has full type information from schema
const result = await db.query(userQueries.create, [data.email, data.password, data.name]);
return result.rows[0];
}
型の再生成
スキーマを更新したときは、型を同期した状態に保つため npm run generate:types を実行してください。
データベース制約との整合
スキーマの検証内容は、データベースの制約に合わせる必要があります。
| データベースの制約 | JSON Schema での指定 |
|---|---|
NOT NULL | required 配列に含めます |
UNIQUE | サービス層で検証します(スキーマでは検証しません) |
CHECK (status IN ('a', 'b')) | "enum": ["a", "b"] |
VARCHAR(255) | "maxLength": 255 |
CHECK (amount > 0) | "minimum": 1(排他的な場合は "exclusiveMinimum": 0) |
OpenAPI との統合
OpenAPI 仕様からスキーマを参照します。
# openapi.yaml
paths:
/users:
post:
requestBody:
required: true
content:
application/json:
schema:
$ref: './schemas/entities/user.create.schema.json'
responses:
'201':
content:
application/json:
schema:
$ref: './schemas/entities/user.schema.json'
'422':
$ref: '#/components/responses/ValidationError'
tsc による型チェック
このプロジェクトでは、JSDoc アノテーション付きの JavaScript ファイルの型チェックに tsc --checkJs を使用します。
型チェックの実行
npm run typecheck
要件
型チェックには以下が必要です:
- @types パッケージをインストールするための
npm install - ファイルに JSDoc の型注釈が記述されていること
jsconfig.json の設定
{
"compilerOptions": {
"checkJs": true,
"strict": true,
"skipLibCheck": true,
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext"
},
"include": ["src/**/*.js", "test/**/*.js"],
"exclude": ["node_modules"]
}
テンプレート構文の処理
テンプレート構文({{VARIABLE}})を含むファイルは、プロジェクトの作成時に処理されます。プロジェクトが生成されるまでは、型チェックの対象から除外してください。
スターターテンプレートの場合:
config/index.jsのようなファイルには、{{PROJECT_NAME}}のプレースホルダーが含まれています- テンプレート処理後は、有効な JavaScript になります
- 生成されたプロジェクトで
npm installを実行した後は、型チェックが正しく実行されます
一般的な型のパターン
// Import Express types
/**
* @typedef {import('express').Request} Request
* @typedef {import('express').Response} Response
* @typedef {import('express').NextFunction} NextFunction
*/
// Type middleware parameters
/**
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
export function myMiddleware(req, res, next) {
// ...
}
// Import schema types (after npm run generate:types)
/**
* @typedef {import('./types/generated/entities/user.create').UserCreate} CreateUserInput
*/
ベストプラクティス
- 信頼できる唯一の情報源 - スキーマでバリデーション、型、ドキュメントを定義します
- デフォルトで厳格に - 常に
additionalProperties: falseを使用します - わかりやすいエラーメッセージ - すべてのプロパティで
descriptionを使用します - 多層防御 - API の境界とデータベースへの書き込み前の両方でバリデーションを行います
- データベースとの整合性 - スキーマの制約を CHECK 制約と一致させます
- 重複せず生成する - TypeScript では
npm run generate:typesを使用します - JSDoc の型を追加する - エクスポートするすべての関数に、引数と戻り値の型を記述します
関連スキル
rest-api- API エンドポイントのパターンと HTTP ステータスコードerror-handling- ValidationError を含むカスタムエラークラスforms- クライアント側の HTML5 バリデーション(UX レイヤー)security- 入力のサニタイズと出力のエンコードtypescript-author- TypeScript のパターンと Zod の代替手段