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

⚙️ ハンドラー

handlerは、APIエンドポイントのコアビジネスロジックを含む非同期関数です。リクエストデータとコンテキストを含むペイロードを受け取り、レスポンスデータを含む同じペイロードオブジェクトを返します。WebSocketルートの場合、handlersはリアルタイム通信のためにRxJS Subjectsを返します。


📋 Handlerタイプ

Nodeblocksは、役割とプロトコルに基づいて2つの主要なhandlerタイプを提供します:

HTTPハンドラー

HTTPハンドラーは、ビジネスロジック操作(データベース呼び出し、データバリデーション、変換など)を実行し、Result<RouteHandlerPayload, NodeblocksError>を返すhandlersです:

import { ok, err, Result } from 'neverthrow';
import { primitives, handlers, utils } from '@nodeblocks/backend-sdk';

const { mergeData } = handlers;
const { createBaseEntity } = utils;

// データベース操作のための非同期handler
export const createUser: primitives.AsyncRouteHandler<
Result<primitives.RouteHandlerPayload, primitives.NodeblocksError>
> = async (payload) => {
const { params, context } = payload;

if (!params.requestBody || Object.keys(params.requestBody).length === 0) {
return err(
new primitives.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 primitives.NodeblocksError(400, 'Failed to create user', 'createUser')
);
}

return ok(mergeData(payload, { userId: baseEntity.id }));
} catch (_error) {
return err(new primitives.NodeblocksError(500, 'Failed to create user', 'createUser'));
}
};

// データ変換のための同期handler
export const formatUserData: primitives.RouteHandler<
Result<primitives.RouteHandlerPayload, primitives.NodeblocksError>
> = (payload) => {
const { requestBody } = payload.params;

const user = {
name: `${requestBody.firstName} ${requestBody.lastName}`
};

return ok(mergeData(payload, { user }));
};

ターミネーターハンドラー

Terminator handlersは、すべてのHTTP handlerチェーンの最後のhandlersです。 これらは前のhandlersからResultを受け取り、エラーをチェックし、最終的なHTTPレスポンスをフォーマットします:

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

export const normalizeUserTerminator = (
result: Result<primitives.RouteHandlerPayload, primitives.NodeblocksError>
) => {
if (result.isErr()) {
throw result.error;
}
const payload = result.value;
const { context } = payload;

if (!context.data?.user) {
throw new primitives.NodeblocksError(
500,
'Unknown error normalizing user',
'normalizeUserTerminator'
);
}
const { _id, ...user } = context.data.user;
return user;
};

重要なパターン: Terminator handlersは常に:

  1. result.isErr()をチェックし、失敗した操作に対してthrow result.errorを実行します
  2. result.valueでペイロードを抽出し、payload.context.dataからデータを取得します
  3. 最終的なフォーマットされたレスポンスオブジェクトを返します(Result型ではありません)

注意: Handlersは、データベース呼び出しやAPIリクエストなどの非同期操作を実行するかどうかに応じて、同期(RouteHandler)または非同期(AsyncRouteHandler)のいずれかになります。

WebSocketハンドラー

WebSocketハンドラーは、双方向通信のためにRxJS Subjectsを返すリアルタイムWebSocketルートのhandlersです:

import { Subject } from 'rxjs';
import { filter } from 'rxjs/operators';
import { primitives, types } from '@nodeblocks/backend-sdk';

export const realtimeHandler = (payload: primitives.WsRouteHandlerPayload) => {
const subject = new Subject();

// リアルタイムデータソースのセットアップ
const changeStream = payload.context.db.data.watch();

changeStream.on('change', (changeDoc) => {
subject.next({
type: 'update',
data: changeDoc.fullDocument,
timestamp: Date.now()
});
});

// 接続が閉じたときのクリーンアップ
subject.subscribe({
complete: () => changeStream.close(),
error: () => changeStream.close()
});

return subject;
};

🔄 ペイロード構造

すべてのhandlerは、次の構造を持つペイロードオブジェクトを受け取ります:

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; 無効化
[entityName: string]: any;
};
logger?: Logger;
[prop: string]: any;
}

🧑‍💻 完全なHandlerチェーンの例

handlersとterminator handlersが完全なチェーンでどのように連携するかを示します:

import { primitives, handlers } from '@nodeblocks/backend-sdk';
import { ok, err, Result } from 'neverthrow';

const { compose, lift, withRoute } = primitives;
const { mergeData } = handlers;

// Handler: データベース操作(Resultを返す)
export const getUserById: primitives.AsyncRouteHandler<
Result<primitives.RouteHandlerPayload, primitives.NodeblocksError>
> = async (payload) => {
const { context, params } = payload;
const userId = context.data?.userId || params.requestParams?.userId;

if (!userId) {
return err(new primitives.NodeblocksError(400, 'User ID is required', 'getUserById'));
}

try {
const user = await context.db.users.findOne({ id: String(userId) });
if (!user) {
return err(new primitives.NodeblocksError(404, 'User not found', 'getUserById'));
}
return ok(mergeData(payload, { user }));
} catch (_error) {
return err(new primitives.NodeblocksError(500, 'Failed to get user', 'getUserById'));
}
};

// Terminator handler: 最終レスポンスをフォーマット(Resultを処理し、フォーマットされたデータを返す)
export const sanitizeUserTerminator = (result: Result<primitives.RouteHandlerPayload, primitives.NodeblocksError>) => {
if (result.isErr()) {
throw result.error;
}
const payload = result.value;
const { context } = payload;

if (!context.data?.user) {
throw new primitives.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: 最終レスポンスをフォーマット
),
});

🛠️ 関連ユーティリティ

handlerコンポーネントは、いくつかのユーティリティ関数と密接に連携します:


➡️ 次へ

Schema »を学習してデータがどのように形成されるかを確認するか、WebSocket Services »を探索してリアルタイムhandlerパターンを確認してください。