🛣️ ルート
routeは、HTTPメソッド、パス、バリデーター、ビジネスロジックをまとめます。Nodeblocksは、メタデータを保存し、サービスルーターが生成されるときにExpressに自動的に接続するwithRouteヘルパーを提供します。ルートは、リアルタイム通信のためにHTTPとWebSocketの両方のプロトコルをサポートします。
1️⃣ 構造
ルートはwithRouteヘルパーを使用して定義され、HTTPメソッド、パス、バリデーター、handlerロジックをまとめます:
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チェーンは次のパターンに従います:
- Blocks or Handlers - ビジネスロジックを実行(データベース操作、バリデーション、変換)
- Terminator Handler - HTTPレスポンスをフォーマットする最終handler
推奨: Blocksでの合成
推奨されるアプローチは、applyPayloadArgsを使用してblocksを直接使用してルートを合成することです。Blocksはビジネスロジックを含む純粋な関数で、ルートで直接合成できます:
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を使用します。これはまだサポートされていますが、新しいコードには推奨されません:
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️⃣ ベストプラクティス
- Path semantics – リソースには名詞(
/users)、アクションには動詞(/users/:id/lock)。 - Complete handler chains – 常に最終レスポンスをフォーマットするterminator handlerで終了します。
- Use blocks – 再利用性とテスト容易性のために、カスタムhandlersよりもblocksを直接合成することを優先します。
- Keep logic small – 単一のモノリスよりも複数の合成可能なものを優先します。
- Validators first – DB呼び出しの前に軽量チェックを実行します。
- 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 | 必須(GET、POSTなど) | 該当なし |
| ハンドラー戻り値 | データオブジェクトまたはResult | RxJS Subject |
| 通信 | リクエスト-レスポンス | 双方向ストリーミング |
| 使用例 | REST API、CRUD操作 | リアルタイム更新、ライブデータ |
➡️ 次へ
Feature »を学習して、ルートとスキーマを再利用可能なユニットに合成する方法を確認するか、WebSocket Services »を探索してリアルタイム機能を確認してください。