🔧 ハンドラー
ハンドラーは、APIエンドポイントのコアビジネスロジックを含む非同期関数です。リクエストデータとコンテキストを含むペイロードを受け取り、レスポンスデータを含む同じペイロードオブジェクトを返します。
📋 ハンドラー種類
Nodeblocksは、ハンドラーチェーンでの役割に基づいて2つの主要なハンドラー種類を提供します:
ハンドラー
ビジネスロジック操作(データベース呼び出し、データ検証、変換など)を実行し、Result<RouteHandlerPayload, NodeblocksError>
を返すハンドラー:
import { ok, err, Result } from 'neverthrow';
import { primitives, handlers, utils } from '@nodeblocks/backend-sdk';
const { NodeblocksError, AsyncRouteHandler, RouteHandlerPayload } = primitives;
const { mergeData } = handlers;
const { createBaseEntity } = utils;
// データベース操作用の非同期ハンドラー
export const createUser: AsyncRouteHandler<
Result<RouteHandlerPayload, NodeblocksError>
> = async (payload) => {
const { params, context } = payload;
if (!params.requestBody || Object.keys(params.requestBody).length === 0) {
return err(
new NodeblocksError(400, 'リクエストボディが必要です', 'createUser')
);
}
const baseEntity = createBaseEntity(params.requestBody);
try {
const createdUser = await context.db.users.insertOne(baseEntity);
if (!createdUser.insertedId) {
return err(
new NodeblocksError(400, 'ユーザーの作成に失敗しました', 'createUser')
);
}
return ok(mergeData(payload, { userId: baseEntity.id }));
} catch (_error) {
return err(new NodeblocksError(500, 'ユーザーの作成に失敗しました', 'createUser'));
}
};
// データ変換用の同期ハンドラー
export const formatUserData: RouteHandler<
Result<RouteHandlerPayload, NodeblocksError>
> = (payload) => {
const { requestBody } = payload.params;
const user = {
name: `${requestBody.firstName} ${requestBody.lastName}`
};
return ok(mergeData(payload, { user }));
};
注意: ハンドラーは、データベース呼び出しやAPIリクエストなどの非同期操作を実行するかどうかに応じて、同期(
RouteHandler
)または非同期(AsyncRouteHandler
)のいずれかにできます。
ターミネーターハンドラー
ターミネーターハンドラーは、すべてのハンドラーチェーンの最終ハンドラーです。 前のハンドラーからResult
を受け取り、エラーをチェックし、最終的なHTTPレスポンスをフォーマットします:
import { primitives } from '@nodeblocks/backend-sdk';
import { Result } from 'neverthrow';
const { NodeblocksError, RouteHandlerPayload } = primitives;
export const normalizeUserTerminator = (
result: Result<RouteHandlerPayload, NodeblocksError>
) => {
if (result.isErr()) {
throw result.error;
}
const payload = result.value;
const { context } = payload;
if (!context.data?.user) {
throw new NodeblocksError(
500,
'ユーザーの正規化で不明なエラー',
'normalizeUserTerminator'
);
}
const { _id, ...user } = context.data.user;
return user;
};
重要: ターミネーターハンドラーは
Result
を受け取り、エラーの場合は例外をスローし、成功の場合は最終レスポンスデータを返します。
🔗 ハンドラーコンポジション
ハンドラーはcompose
関数を使用してチェーンされ、複雑な操作を構築できます:
import { compose } from '@nodeblocks/backend-sdk';
const createUserPipeline = compose(
validateUserInput,
createUser,
sendWelcomeEmail,
normalizeUserTerminator
);
このパターンにより、各ハンドラーが単一の責任を持ち、再利用可能で簡単にテストできるようになります。
🎯 ベストプラクティス
1. 単一責任
各ハンドラーは1つの明確な目的を持つべきです:
// 良い - 単一責任
const validateUserEmail = (payload) => { /* メール検証のみ */ };
const createUserAccount = (payload) => { /* アカウント作成のみ */ };
// 悪い - 複数責任
const validateAndCreateUser = (payload) => { /* 検証と作成の両方 */ };
2. エラーハンドリング
常に適切なエラー情報を含めてください:
return err(
new NodeblocksError(
400, // HTTPステータスコード
'詳細なエラーメッセージ', // ユーザー向けメッセージ
'handlerFunctionName' // デバッグ用の関数名
)
);
3. 型安全性
TypeScriptの型を使用してハンドラーを定義:
export const myHandler: AsyncRouteHandler<
Result<RouteHandlerPayload, NodeblocksError>
> = async (payload) => {
// 実装
};
4. テスタビリティ
ハンドラーを純粋関数として設計し、すべての依存関係をコンテキストを通じて注入:
export const createUser = async (payload: RouteHandlerPayload) => {
const { context } = payload;
// context.db、context.configurationなどを使用
};
📚 利用可能なハンドラーユーティリティ
データマージ
import { mergeData } from '@nodeblocks/backend-sdk';
const result = mergeData(payload, { newData: 'value' });
エンティティ作成
import { createBaseEntity } from '@nodeblocks/backend-sdk';
const entity = createBaseEntity(requestData);
// 自動的にid、createdAt、updatedAtを追加
結果処理
import { ok, err } from 'neverthrow';
// 成功の場合
return ok(updatedPayload);
// エラーの場合
return err(new NodeblocksError(400, 'エラーメッセージ', 'handlerName'));
➡️ 次へ
ルートコンポーネントについて学習して、ハンドラーがHTTPエンドポイントとどのように統合されるかを理解しましょう!