メインコンテンツまでスキップ
バージョン: 0.5.0 (最新)

🔍 バリデータ

バリデータは、スキーマ検証を超えた追加の検証を実行する関数です。ルートハンドラーの前に実行され、ビジネスロジック、リソースの存在、認証などをチェックできます。


🔍 バリデータとは何ですか?

バリデータは、ハンドラーの前にリクエストパイプラインで実行される述語関数です。以下のことができます:

  • ビジネスロジックの検証(例:「このリソースは存在しますか?」)
  • 認証のチェック(例:「ユーザーは認可されていますか?」)
  • パラメータの検証(例:「これは有効なUUIDですか?」)
  • 制約の適用(例:「ユーザーに権限がありますか?」)
┌─────────────┐
│ リクエスト │
├─────────────┤
│ スキーマ │ ← JSONスキーマ検証
│ バリデータ │ ← カスタムビジネスロジック
│ ハンドラー │ ← ルートロジック
└─────────────┘

主要な特徴:

  • 非同期関数 - データベースクエリを実行可能
  • 失敗時にスロー - 一貫したエラーレスポンスのためにNodeblocksErrorを使用
  • 合成可能 - 複数のバリデータをチェーン可能
  • コンテキスト対応 - データベース、リクエストパラメータ、コンテキストにアクセス可能

📋 利用可能なバリデータ

汎用バリデータ

重要: レガシーパラメータバリデータ

以下のパラメータバリデータは現在レガシーインターフェースを使用しており、後で非推奨となり削除されます:

  • requireParam
  • isUUID
  • isNumber

これらは標準のRouteHandlerPayloadを受け入れません。payload.params.requestParamsを抽出し、小さなラッパー経由で手動で渡す必要があります:

// レガシーパラメータバリデータのラッパーパターン
({ params: { requestParams } }) => requireParam('userId')(requestParams)
({ params: { requestParams } }) => isUUID('userId')(requestParams)
({ params: { requestParams } }) => isNumber('limit')(requestParams)

このレガシーインターフェースのため、これらのバリデータはcompose()some()などの合成ヘルパーに直接渡すことができません。これらのヘルパーでは、標準バリデータまたは通常のペイロード署名を持つカスタムバリデータを使用してください。

requireParam(key: string)

必須パラメータが存在し、nullでないことを確認します。

import { validators } from '@nodeblocks/backend-sdk';

const { requireParam } = validators;

const getUserRoute = withRoute({
method: 'GET',
path: '/users/:userId',
validators: [({params: { requestParams }}) => requireParam('userId')(requestParams)],
handler: getUserHandler,
});

動作:

  • ✅ パラメータが存在し、nullでない場合に通過
  • ❌ パラメータが欠落またはnullの場合にエラーをスロー
  • エラー: Missing required parameter: userId

isUUID(key: string)

パラメータが有効なUUID v4形式に従っていることを検証します。

import { validators } from '@nodeblocks/backend-sdk';

const { isUUID } = validators;

const getCategoryRoute = withRoute({
method: 'GET',
path: '/categories/:categoryId',
validators: [({params: { requestParams }}) => isUUID('categoryId')(requestParams)],
handler: getCategoryHandler,
});

動作:

  • ✅ パラメータが有効なUUID v4形式の場合に通過
  • ❌ パラメータが存在するが無効なUUIDの場合にエラーをスロー
  • エラー: Invalid UUID format for parameter: categoryId
  • 形式: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx

isNumber(key: string)

パラメータが有効な数値に変換可能であることを検証します。

import { validators } from '@nodeblocks/backend-sdk';

const { isNumber } = validators;

const getProductsRoute = withRoute({
method: 'GET',
path: '/products',
validators: [({params: { requestParams }}) => isNumber('limit')(requestParams)],
handler: getProductsHandler,
});

動作:

  • ✅ パラメータが数値に変換可能な場合に通過
  • ❌ パラメータが存在するが数値として解析できない場合にエラーをスロー
  • エラー: Parameter limit must be a number

some(...validators: Validator[])

複数のバリデータを実行し、少なくとも1つが成功した場合に通過します。条件付き検証シナリオに役立ちます。

import { validators } from '@nodeblocks/backend-sdk';

const { some } = validators;

const flexibleUserRoute = withRoute({
method: 'GET',
path: '/users/:identifier',
validators: [
// ここでは標準バリデータを使用してください。requireParam/isUUID/isNumberのような
// レガシーパラメータバリデータは`some()`で直接使用できません。
some(validateHasUserId, validateHasEmail)
],
handler: getUserHandler,
});

動作:

  • ✅ 少なくとも1つのバリデータが成功した場合に通過
  • ❌ すべてのバリデータが失敗した場合にエラーをスロー
  • ユースケース: 代替検証パス(例:ユーザーはIDまたはメールで識別可能)

認証バリデータ

verifyAuthentication

ルートを保護するための高階認証バリデータ。

import { validators } from '@nodeblocks/backend-sdk';

const { verifyAuthentication } = validators;

const getSecretRoute = withRoute({
method: 'GET',
path: '/secret',
validators: [verifyAuthentication(authenticateFunction)],
handler: getSecretHandler,
});

動作:

  • ✅ 認証が成功した場合に通過
  • ❌ 認証が失敗した場合にエラーをスロー
  • 使用法: 認証関数が提供される必要があります

認証関数

認証関数はトークンを検証し、トークン情報を返す関数です。リクエストが適切に認証されているかをチェックするためにバリデータによって使用されます。

getBearerTokenInfo (デフォルト)

AuthorizationヘッダーからのBearerトークンを検証するデフォルトの認証関数。

import { validators, utils } from '@nodeblocks/backend-sdk';

const { validateResourceAccess } = validators;
const { getBearerTokenInfo } = utils;

// デフォルト使用法(getBearerTokenInfoが自動的に使用されます)
const protectedRoute = withRoute({
method: 'GET',
path: '/protected',
validators: [
validateResourceAccess(['admin']) // デフォルトでgetBearerTokenInfoを使用
],
handler: protectedHandler,
});

// 明示的な使用法
const explicitRoute = withRoute({
method: 'GET',
path: '/protected',
validators: [
validateResourceAccess(['admin'], getBearerTokenInfo)
],
handler: protectedHandler,
});

動作原理:

  1. Authorization: Bearer <token>ヘッダーからトークンを抽出
  2. JWTトークン署名を復号化および検証
  3. トークンタイプを検証(ユーザーアクセストークンまたはアプリアクセストークン)
  4. ユーザートークンに対してセキュリティチェックを実行(フィンガープリント、IP、ユーザーエージェント)
  5. 認可決定のためのトークン情報を返す

サポートされるトークンタイプ:

  • User Access Tokens: userIdを含み、セキュリティ検証が必要
  • App Access Tokens: appIdを含み、セキュリティチェックをバイパス

getCookieTokenInfo

Cookieベースのトークン用の代替認証関数。

import { validators } from '@nodeblocks/backend-sdk';

const { getCookieTokenInfo, validateResourceAccess } = validators;

const cookieRoute = withRoute({
method: 'GET',
path: '/cookie-protected',
validators: [
validateResourceAccess(['admin'], getCookieTokenInfo)
],
handler: protectedHandler,
});

ユースケース:

  • Cookieベース認証を使用するWebアプリケーション
  • 異なるセキュリティ検証ルール(IPチェックが無効)
  • サーバー側セッション管理

カスタム認証関数

特定のユースケースのためにカスタム認証関数を作成できます:

import { primitives, validators } from '@nodeblocks/backend-sdk';

const { Authenticator, RouteHandlerPayload, TokenInfo, NodeblocksError } = primitives;

const { validateResourceAccess } = validators;

const customAuth: Authenticator = async (payload: RouteHandlerPayload): Promise<TokenInfo> => {
const { context } = payload;
const { request } = context;

// カスタムトークン抽出ロジック
const customToken = request.headers['x-custom-token'];
if (!customToken) {
throw new NodeblocksError(401, 'Custom token required');
}

// カスタム検証ロジック
// ... ここに検証コードを記述

// トークン情報を返す
return {
accessType: 'user',
userId: 'user-123',
type: 'access'
};
};

// カスタム認証を使用した使用法
const customRoute = withRoute({
method: 'GET',
path: '/custom-protected',
validators: [
validateResourceAccess(['admin'], customAuth)
],
handler: protectedHandler,
});

認証関数シグネチャ:

type Authenticator = (payload: RouteHandlerPayload) => Promise<TokenInfo>;

ドメイン固有のバリデータ

カテゴリサービス

doesCategoryExist

操作を実行する前に、カテゴリがデータベースに存在することを検証します。

import { validators } from '@nodeblocks/backend-sdk';

const { doesCategoryExist } = validators;

const updateCategoryRoute = withRoute({
method: 'PATCH',
path: '/categories/:categoryId',
validators: [doesCategoryExist],
handler: updateCategoryHandler,
});

動作:

  • ✅ カテゴリがデータベースに存在する場合に通過
  • ❌ カテゴリが見つからない場合にNodeblocksError (404)をスロー
  • エラー: Category does not exist (404ステータス)

アクセス制御バリデータ

validateResourceAccess

許可されたサブジェクトとトークン情報に基づいてリソースアクセスを検証します。保護されたリソースのロールベースアクセス制御を実装します。

import { validators } from '@nodeblocks/backend-sdk';

const { validateResourceAccess } = validators;

const adminRoute = withRoute({
method: 'GET',
path: '/admin/users',
validators: [
validateResourceAccess(['admin'])
],
handler: getUsersHandler,
});

const selfAccessRoute = withRoute({
method: 'GET',
path: '/users/:userId',
validators: [
validateResourceAccess(['self', 'admin'])
],
handler: getUserHandler,
});

設定:

{
allowedSubjects: string[] // 許可されたユーザータイプ/サブジェクトの配列
}

サポートされるサブジェクト:

  • 'admin' - 管理者アクセス
  • 'user' - 通常ユーザーアクセス
  • 'guest' - ゲストアクセス
  • 'self' - ユーザーは自分のリソースのみアクセス可能

動作:

  • ✅ ユーザーに適切な権限がある場合に通過
  • ❌ 無効なトークンの場合にNodeblocksError (401)をスロー
  • ❌ 認可されていないアクセスの場合にNodeblocksError (403)をスロー
  • エラー:
    • App token is not valid (401)
    • User token is not valid (401)
    • Identity not found (401)
    • User is not authorized to access this resource (403)

validateChannelAccess

所有権とトークン情報に基づいてチャンネルアクセスを検証します。

import { validators } from '@nodeblocks/backend-sdk';

const { validateChannelAccess } = validators;

const channelRoute = withRoute({
method: 'PATCH',
path: '/channels/:channelId',
validators: [
validateChannelAccess(['owner'])
],
handler: updateChannelHandler,
});

サポートされるサブジェクト:

  • 'owner' - チャンネル所有者アクセス

動作:

  • ✅ ユーザーがチャンネル所有者の場合に通過
  • ❌ 無効なトークンの場合にNodeblocksError (401)をスロー
  • ❌ 認可されていないアクセスの場合にNodeblocksError (403)をスロー
  • ❌ チャンネルが見つからない場合にNodeblocksError (404)をスロー
  • エラー:
    • App token is not valid (401)
    • User token is not valid (401)
    • Channel not found (404)
    • Channel has no owner (403)
    • User is not authorized to access this channel (403)

validateMessageAccess

所有権とトークン情報に基づいてメッセージアクセスを検証します。

import { validators } from '@nodeblocks/backend-sdk';

const { validateMessageAccess } = validators;

const messageRoute = withRoute({
method: 'DELETE',
path: '/messages/:messageId',
validators: [
validateMessageAccess(['owner'])
],
handler: deleteMessageHandler,
});

サポートされるサブジェクト:

  • 'owner' - メッセージ送信者アクセス

動作:

  • ✅ ユーザーがメッセージ送信者の場合に通過
  • ❌ 無効なトークンの場合にNodeblocksError (401)をスロー
  • ❌ 認可されていないアクセスの場合にNodeblocksError (403)をスロー
  • ❌ メッセージが見つからない場合にNodeblocksError (404)をスロー
  • エラー:
    • App token is not valid (401)
    • User token is not valid (401)
    • Message not found (404)
    • Message has no sender (403)
    • User is not authorized to access this message (403)

validateOrderAccess

所有権とトークン情報に基づいてオーダーアクセスを検証します。

import { validators } from '@nodeblocks/backend-sdk';

const { validateOrderAccess } = validators;

const orderRoute = withRoute({
method: 'GET',
path: '/orders/:orderId',
validators: [
validateOrderAccess(['owner'])
],
handler: getOrderHandler,
});

サポートされるサブジェクト:

  • 'owner' - オーダー所有者アクセス

動作:

  • ✅ ユーザーがオーダー所有者の場合に通過
  • ❌ 無効なトークンの場合にNodeblocksError (401)をスロー
  • ❌ 認可されていないアクセスの場合にNodeblocksError (403)をスロー
  • ❌ オーダー見つからない場合にNodeblocksError (404)をスロー
  • エラー:
    • App token is not valid (401)
    • User token is not valid (401)
    • Order not found (404)
    • Order has no user (403)
    • User is not authorized to access this order (403)

validateUserProfileAccess

ユーザータイプと所有権に基づいてユーザープロファイルアクセスを検証します。

import { validators } from '@nodeblocks/backend-sdk';

const { validateUserProfileAccess } = validators;

const profileRoute = withRoute({
method: 'GET',
path: '/users/:userId/profile',
validators: [
validateUserProfileAccess(['self', 'admin'])
],
handler: getUserProfileHandler,
});

サポートされるサブジェクト:

  • 'admin' - 管理者アクセス
  • 'user' - 通常ユーザーアクセス
  • 'guest' - ゲストアクセス
  • 'self' - ユーザーは自分のプロファイルにアクセス可能

動作:

  • ✅ ユーザーに適切な権限がある場合に通過
  • ❌ 無効なトークンの場合にNodeblocksError (401)をスロー
  • ❌ 認可されていないアクセスの場合にNodeblocksError (403)をスロー
  • ❌ ユーザープロファイルが見つからない場合にNodeblocksError (404)をスロー
  • エラー:
    • App token is not valid (401)
    • User token is not valid (401)
    • Identity not found (401)
    • User profile not found (404)
    • Identity is not authorized to access this resource (403)

validateOrganizationAccess

メンバーシップとロールに基づいて組織アクセスを検証します。

import { validators } from '@nodeblocks/backend-sdk';

const { validateOrganizationAccess } = validators;

const orgRoute = withRoute({
method: 'GET',
path: '/organizations/:organizationId',
validators: [
validateOrganizationAccess(['admin', 'member'])
],
handler: getOrganizationHandler,
});

サポートされるサブジェクト:

  • 'admin' - 組織管理者
  • 'member' - 組織メンバー
  • 'owner' - 組織所有者

動作:

  • ✅ ユーザーが適切なロールを持つ組織メンバーの場合に通過
  • ❌ 無効なトークンの場合にNodeblocksError (401)をスロー
  • ❌ 認可されていないアクセスの場合にNodeblocksError (403)をスロー
  • ❌ 組織が見つからない場合にNodeblocksError (404)をスロー
  • エラー:
    • App token is not valid (401)
    • User token is not valid (401)
    • Organization not found (404)
    • Organization has no users (403)
    • User does not belong to this organization (403)
    • User is not authorized to access this organization (403)

🛠️ カスタムバリデータの作成

バリデータ関数シグネチャ

type Validator = (payload: RouteHandlerPayload) => Promise<void>;

基本的なバリデータの例

import { primitives } from '@nodeblocks/backend-sdk';

const { NodeblocksError } = primitives;

const validateUserExists = async (payload: RouteHandlerPayload) => {
const { context, params } = payload;
const userId = params.requestParams?.userId;

const user = await context.db.users.findOne({ id: userId });
if (!user) {
throw new NodeblocksError(404, 'User not found', 'USER_NOT_FOUND');
}
};

高度なバリデータの例

import { primitives } from '@nodeblocks/backend-sdk';

const { NodeblocksError, RouteHandlerPayload } = primitives;

const validateUserPermission = async (payload: RouteHandlerPayload) => {
const { context, params } = payload;
const { userId, organizationId } = params.requestParams;

// ユーザーが存在するかチェック
const user = await context.db.users.findOne({ id: userId });
if (!user) {
throw new NodeblocksError(404, 'User not found', 'USER_NOT_FOUND');
}

// ユーザーが組織に属しているかチェック
const membership = await context.db.memberships.findOne({
userId,
organizationId,
status: 'active'
});

if (!membership) {
throw new NodeblocksError(403, 'Access denied', 'ACCESS_DENIED');
}
};

ファクトリ関数パターン

再利用可能なバリデータファクトリを作成します:

const requireResourceExists = (collectionName: string, paramName: string) => {
return async (payload: RouteHandlerPayload) => {
const { context, params } = payload;
const resourceId = params.requestParams?.[paramName];

const resource = await context.db[collectionName].findOne({ id: resourceId });
if (!resource) {
throw new NodeblocksError(
404,
`${collectionName} not found`,
'RESOURCE_NOT_FOUND'
);
}
};
};

// 使用法
const validateProductExists = requireResourceExists('products', 'productId');
const validateOrderExists = requireResourceExists('orders', 'orderId');

🔗 ルートでのバリデータの使用

単一バリデータ

export const getCategoryRoute = withRoute({
method: 'GET',
path: '/categories/:categoryId',
validators: [doesCategoryExist],
handler: getCategoryHandler,
});

複数バリデータ

export const updateUserRoute = withRoute({
method: 'PATCH',
path: '/users/:userId',
validators: [
({params: { requestParams }}) => requireParam('userId')(requestParams),
({params: { requestParams }}) => isUUID('userId')(requestParams),
validateUserExists,
verifyAuthentication(authenticate)
],
handler: updateUserHandler,
});

認証 + 認可

export const protectedUserRoute = withRoute({
method: 'PATCH',
path: '/users/:userId',
validators: [
({params: { requestParams }}) => requireParam('userId')(requestParams),
({params: { requestParams }}) => isUUID('userId')(requestParams),
verifyAuthentication(authenticate),
validateResourceAccess(['admin', 'self'])
],
handler: updateUserHandler,
});

条件付きバリデータ

const validators = [
({params: { requestParams }}) => requireParam('userId')(requestParams),
({params: { requestParams }}) => isUUID('userId')(requestParams),
// 非公開ルートでのみ認証をチェック
...(isPublicRoute ? [] : [verifyAuthentication(authenticate)])
];

export const getUserRoute = withRoute({
method: 'GET',
path: '/users/:userId',
validators,
handler: getUserHandler,
});

代替検証パス

export const flexibleUserRoute = withRoute({
method: 'GET',
path: '/users/:identifier',
validators: [
// ここでは標準バリデータを使用してください。requireParam/isUUID/isNumberのような
// レガシーパラメータバリデータは`some()`で直接使用できません。
some(validateHasUserId, validateHasEmail),
validateUserExists
],
handler: getUserHandler,
});

🚨 エラーハンドリング

NodeblocksErrorの使用

一貫したエラーレスポンスのために常にNodeblocksErrorを使用します:

import { primitives } from '@nodeblocks/backend-sdk';

const { NodeblocksError, RouteHandlerPayload } = primitives;

const validateResource = async (payload: RouteHandlerPayload) => {
// 検証ロジック
if (!isValid) {
throw new NodeblocksError(
400, // HTTPステータスコード
'Validation failed', // 人間が読めるメッセージ
'VALIDATION_ERROR' // クライアント処理のためのエラーコード
);
}
};

エラーレスポンス形式

バリデータがエラーをスローすると、クライアントは以下を受け取ります:

{
"error": {
"message": "Category does not exist"
}
}

📋 バリデータリファレンス

バリデータ目的パラメータ戻り値スロー
requireParamパラメータの存在をチェックkey: stringリクエストパラメータを検証する関数null/欠落の場合にエラー
isUUIDUUID形式を検証key: stringリクエストパラメータを検証する関数無効なUUIDの場合にエラー
isNumber数値形式を検証key: stringリクエストパラメータを検証する関数数値でない場合にエラー
some代替検証パス...validators: Validator[]Validatorすべて失敗した場合にエラー
doesCategoryExistカテゴリが存在するかチェックnoneValidatorNodeblocksError (404)
verifyAuthentication認証を検証authenticate functionValidator認証エラー
validateResourceAccessリソース権限をチェックallowedSubjects: string[], authenticate functionValidatorNodeblocksError (401/403)
validateChannelAccessチャンネル所有権をチェックallowedSubjects: string[], authenticate functionValidatorNodeblocksError (401/403/404)
validateMessageAccessメッセージ所有権をチェックallowedSubjects: string[], authenticate functionValidatorNodeblocksError (401/403/404)
validateOrderAccessオーダー所有権をチェックallowedSubjects: string[], authenticate functionValidatorNodeblocksError (401/403/404)
validateUserProfileAccessユーザープロファイルアクセスをチェックallowedSubjects: string[], authenticate functionValidatorNodeblocksError (401/403/404)
validateOrganizationAccess組織アクセスをチェックallowedSubjects: string[], authenticate functionValidatorNodeblocksError (401/403/404)

📐 ベストプラクティス

1. Fail Fast

  • バリデータはハンドラーの前に実行されるため、無効なリクエストに対して早期に失敗する
  • 必要な場合を除き、バリデータで高価な操作を実行しない

2. 再利用可能なパターン

  • 一般的な検証パターンのファクトリ関数を作成
  • ビジネスロジックにドメイン固有のバリデータを使用

3. 明確なエラーメッセージ

  • 説明的なエラーメッセージを提供
  • クライアント側処理のために一貫したエラーコードを使用

4. データベース効率

  • バリデータクエリにデータベースインデックスを使用
  • 頻繁にアクセスされるデータにはキャッシュを検討

5. 合成

  • 論理的にバリデータをチェーン(必須 → 形式 → ビジネスロジック)
  • バリデータを単一の責任に集中させる

6. アクセス制御

  • 保護されたリソースに適切なアクセス制御バリデータを使用
  • 認証と認可バリデータを組み合わせる
  • 柔軟な検証シナリオのためにsome()の使用を検討

➡️ 次へ