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

🛣️ ルート

routeは、HTTPメソッド、パス、バリデーター、ビジネスロジックをまとめます。Nodeblocksは、メタデータを保存し、サービスルーターが生成されるときにExpressに自動的に接続するwithRouteヘルパーを提供します。ルートは、リアルタイム通信のためにHTTPとWebSocketの両方のプロトコルをサポートします。


1️⃣ 構造

ルートはwithRouteヘルパーを使用して定義され、HTTPメソッド、パス、バリデーター、handlerロジックをまとめます:

src/routes/user.ts
import { primitives, blocks, validators } from '@nodeblocks/backend-sdk';

const { compose, withRoute } = primitives;
const { isAuthenticated } = validators;
const { getUserById } = blocks;

export const getUserRoute = withRoute({
method: 'GET',
path: '/users/:userId',
validators: [isAuthenticated()],
handler: compose(/* handler chain */),
});

以下はwithRouteによって返される最小限の形状です。

interface Route {
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; // HTTPルートに必須
protocol?: 'http' | 'ws'; // プロトコルを指定(デフォルトは'http')
path: string; // Expressスタイルのパス、:paramsをサポート
validators?: Validator[]; // handlerチェーンの**前に**実行されるオプションリスト
handler: RouteHandler; // 合成可能な関数チェーン
}

プロトコル

ルートは2つのプロトコルをサポートします:

プロトコル目的Handler戻り値タイプ
http従来のREST APIエンドポイントデータオブジェクトまたはResult
wsリアルタイムWebSocket接続RxJS Subject

メソッド

REST規則に従うHTTP動詞(HTTPルートのみ):

動詞目的
GETリソースの読み取り/取得
POST新しいリソースの作成
PATCHリソースの部分更新
DELETEリソースの削除

パス

Expressスタイルのテンプレート。コレクションには複数形の名詞/users)を使用し、アクションにはサブパス(/users/:id/lock)を使用します。動的な部分はrequestParamsになります。

バリデーター

データベース作業の前に実行される同期または非同期フック。これらは同じpayloadオブジェクトを受け取ります。
リクエストを拒否する慣用的な方法は、throw new NodeblocksError(status, message)です:

import {NodeblocksError} from '../primitives/error';

export const validateEmail: Validator = ({ params }) => {
if (!params.requestBody.email?.includes('@')) {
throw new NodeblocksError(400, 'Invalid email');
}
};

ハンドラー

ルートはビジネスロジックをhandler chainsに委譲します—リクエストを処理し、レスポンスをフォーマットする合成関数。すべてのhandlerチェーンは次のパターンに従います:

  1. Blocks or Handlers - ビジネスロジックを実行(データベース操作、バリデーション、変換)
  2. Terminator Handler - HTTPレスポンスをフォーマットする最終handler

推奨: Blocksでの合成

推奨されるアプローチは、applyPayloadArgsを使用してblocksを直接使用してルートを合成することです。Blocksはビジネスロジックを含む純粋な関数で、ルートで直接合成できます:

src/routes/user.ts
import { primitives, blocks, validators } from '@nodeblocks/backend-sdk';

const { compose, flatMapAsync, withRoute, lift, applyPayloadArgs, orThrow } = primitives;
const { isAuthenticated } = validators;
const { getUserById, normalizeUser, UserNotFoundBlockError, UserBlockError } = blocks;

export const getUserRoute = withRoute({
method: 'GET',
path: '/users/:userId',
validators: [isAuthenticated()],
handler: compose(
// applyPayloadArgsでblockを直接使用
applyPayloadArgs(
getUserById,
[
['context', 'db', 'users'],
['params', 'requestParams', 'userId']
],
'user'
),
// 追加のblocksをチェーン
flatMapAsync(
applyPayloadArgs(
normalizeUser,
[['context', 'data', 'user']],
'normalizedUser'
)
),
// Terminatorはエラーマッピングで最終レスポンスをフォーマット
lift(
orThrow(
[
[UserNotFoundBlockError, 404],
[UserBlockError, 500],
],
[['context', 'data', 'normalizedUser'], 200]
)
)
),
});

blocksを使用する主な利点:

  • Pure functions - ペイロードモックなしでテストが容易
  • Reusable - 同じblocksを異なるルート間で使用可能
  • Composable - 複数のblocksをシームレスにチェーン
  • Type-safe - より良いTypeScript推論

レガシー: Handlersでの合成

レガシーアプローチは、ビジネスロジックをラップするカスタムhandlersを使用します。これはまだサポートされていますが、新しいコードには推奨されません:

src/routes/user.ts (legacy)
import { primitives, handlers } from '@nodeblocks/backend-sdk';
import {createUser, getUserById, normalizeUserTerminator} from '../handlers';

const { compose, flatMapAsync, withRoute, lift } = primitives;

export const createUserRoute = withRoute({
method: 'POST',
path: '/users',
validators: [isAuthenticated()],
handler: compose(
createUser, // Handler: DBに書き込み
flatMapAsync(getUserById), // Handler: 作成されたエンティティを読み戻し
lift(normalizeUserTerminator) // Terminator handler: 最終HTTPレスポンスをフォーマット
),
});

Note: blocksの詳細については、Blocks »を参照してください。handlerパターンについては、Handler explanation »を参照してください。


2️⃣ ベストプラクティス

  1. Path semantics – リソースには名詞(/users)、アクションには動詞(/users/:id/lock)。
  2. Complete handler chains – 常に最終レスポンスをフォーマットするterminator handlerで終了します。
  3. Use blocks – 再利用性とテスト容易性のために、カスタムhandlersよりもblocksを直接合成することを優先します。
  4. Keep logic small – 単一のモノリスよりも複数の合成可能なものを優先します。
  5. Validators first – DB呼び出しの前に軽量チェックを実行します。
  6. Stateless – ルートはグローバル状態を変更すべきではありません。すべてはpayload経由で到着します。

推奨: Block合成パターン

const { block1, block2, BlockError1, BlockError2 } = blocks;

handler: compose(
applyPayloadArgs(block1, [/* paths */], 'result1'), // Block: ビジネスロジック
flatMapAsync(applyPayloadArgs(block2, [/* paths */], 'result2')), // Block: 追加ロジック
lift(orThrow([[BlockError1, 404], [BlockError2, 500]], [['context', 'data', 'result2']])) // Terminator: エラーをマッピングし、レスポンスをフォーマット
)

レガシー: Handlerチェーンパターン

handler: compose(
handler1, // ビジネスロジック: Resultを返す
flatMapAsync(handler2), // 追加のビジネスロジック: Resultを返す
lift(terminatorHandler) // レスポンスフォーマット: Resultを処理し、データを返す
)

3️⃣ WebSocketルート

WebSocketルートは、静的データの代わりにRxJS Subjectsを返すことで、リアルタイム通信を可能にします:

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

const { withRoute } = primitives;

export const realtimeDataRoute = withRoute({
protocol: 'ws',
path: '/api/realtime',
handler: (payload) => {
const subject = new Subject();

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

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

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

return subject;
},
validators: [
// WebSocketルートもvalidatorsを使用できます
(payload) => {
// WebSocket接続ヘッダーなどをバリデート
return Promise.resolve();
}
]
});

WebSocketとHTTPルートの比較

アスペクトHTTPルートWebSocketルート
プロトコル'http' (デフォルト)'ws'
Method必須(GETPOSTなど)該当なし
ハンドラー戻り値データオブジェクトまたはResultRxJS Subject
通信リクエスト-レスポンス双方向ストリーミング
使用例REST API、CRUD操作リアルタイム更新、ライブデータ

➡️ 次へ

Feature »を学習して、ルートとスキーマを再利用可能なユニットに合成する方法を確認するか、WebSocket Services »を探索してリアルタイム機能を確認してください。