🔧 ハンドラー
ハンドラーは、APIエンドポイントのコアビジネスロジックを含む非同期関数です。リクエストデータとコンテキストを含むペイロードを受け取り、レスポンスデータを含む同じペイロードオブジェクトを返します。
📋 ハンドラータイプ
NodeBlocksは、ハンドラーチェーンにおける役割に基づいて2つの主要なハンドラータイプを提供します:
Handlers
ビジネスロジック操作(データベース呼び出し、データ検証、変換など)を実行し、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, 'Request body is required', 'createUser')
);
}
const baseEntity = createBaseEntity(params.requestBody);
try {
const createdUser = await context.db.users.insertOne(baseEntity);
if (!createdUser.insertedId) {
return err(
new NodeblocksError(400, 'Failed to create user', 'createUser')
);
}
return ok(mergeData(payload, { userId: baseEntity.id }));
} catch (_error) {
return err(new NodeblocksError(500, 'Failed to create user', '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
)のいずれかになります。
Terminator Handlers
ターミネータハンドラーは、すべてのハンドラーチェーンの最終ハンドラーです。 前のハンドラーから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,
'Unknown error normalizing user',
'normalizeUserTerminator'
);
}
const { _id, ...user } = context.data.user;
return user;
};
主要なパターン: ターミネータハンドラーは常に:
- 失敗した操作に対して
result.isErr()
をチェックし、throw result.error
を実行result.value
でペイロードを抽出し、payload.context.data
からデータを取得- 最終的にフォーマットされたレスポンスオブジェクトを返す(Result型ではない)
🔄 ペイロード構造
すべてのハンドラーは、以下の構造を持つペイロードオブジェクトを受け取ります:
interface HandlerPayload {
params: {
requestBody?: Record<string, any>; // POST/PATCH リクエストボディ
requestParams?: Record<string, any>; // URLパラメータ (:userId)
requestQuery?: Record<string, any>; // クエリ文字列パラメータ
};
context: {
db: any;
mailService?: MailService;
// request: express.Request; disabled
[entityName: string]: any;
};
logger?: Logger;
[prop: string]: any;
}
🧑💻 完全なハンドラーチェーン例
ハンドラーとターミネータハンドラーが完全なチェーンでどのように連携するかを示します:
import { primitives, handlers } from '@nodeblocks/backend-sdk';
import { ok, err, Result } from 'neverthrow';
const { AsyncRouteHandler, RouteHandlerPayload, NodeblocksError, lift } = primitives;
const { mergeData } = handlers;
// Handler: データベース操作(Resultを返す)
export const getUserById: AsyncRouteHandler<
Result<RouteHandlerPayload, NodeblocksError>
> = async (payload) => {
const { context, params } = payload;
const userId = context.data?.userId || params.requestParams?.userId;
if (!userId) {
return err(new NodeblocksError(400, 'User ID is required', 'getUserById'));
}
try {
const user = await context.db.users.findOne({ id: String(userId) });
if (!user) {
return err(new NodeblocksError(404, 'User not found', 'getUserById'));
}
return ok(mergeData(payload, { user }));
} catch (_error) {
return err(new NodeblocksError(500, 'Failed to get user', 'getUserById'));
}
};
// Terminator handler: 最終レスポンスをフォーマット(Resultを処理し、フォーマットされたデータを返す)
export const sanitizeUserTerminator = (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, 'User data not available', 'sanitizeUserTerminator');
}
return {
id: context.data.user.id,
email: context.data.user.email
};
};
// ルートでどのように連携して使用されるか
export const getUserRoute = withRoute({
method: 'GET',
path: '/users/:userId',
handler: compose(
getUserById, // Handler: DBからユーザーを取得
lift(sanitizeUserTerminator) // Terminator: 最終レスポンスをフォーマット
),
});
🛠️ 関連ユーティリティ
ハンドラーコンポーネントは、いくつかのユーティリティ関数と密接に連携します:
- Composition Utilities -
mergeData
と関数合成用 - Entity Utilities -
createBaseEntity
とエンティティ管理用
➡️ 次へ
データの形状を確認する方法についてはSchema »を学習してください。