🛣️ ルート
ルートは、プロトコル、HTTP メソッド、パス、バリデーター、ハンドラーチェーンをまとめたものです。withRoute ヘルパーは RouteComposer、すなわち ServiceDefinition にルートメタデータを追加する関数を返します。ルートを Express に直接接続することはありません。機能へ合成し、defService でマウントします。
ルートは HTTP(デフォルト)と WebSocket(protocol: 'ws')の両方をサポートします。
🔍 ルートとは?
withRoute は (service: ServiceDefinition) => ServiceDefinition を返します。service.routes にルートを追加(または置換)します。先行する withSchema が service.withNextRoute を設定している場合、ルート登録時にそのルートハンドラーへバリデーションが注入されます。
Express の接続は defService(adapter) 実行時に行われます。これは Express ルーターを構築して HTTP ルートを登録し、渡された WebSocketServer に WebSocket ハンドラーを接続します。
ルートのライフサイクル
routes/*.ts → features/*.ts → services/*.ts → defService()
(withRoute) (スキーマとルートを合成) (機能を合成) (express.Router)
- 定義 —
src/routes/<domain>.tsからwithRoute({ method, path, validators, handler })をエクスポートします。 - スキーマと組み合わせる —
src/features/<domain>.tsでcompose(schema, route)を使用します(スキーマはwithNextRouteを設定します)。 - まとめる — サービスファクトリーで機能を結合します。
- 提供する —
defService(partial(compose(...features), [deps]))が Express ルーターを生成します。
withRoute は、同じ method + path + protocol を持つ既存のルートを置換します。スキーマとの組み合わせについては機能 »およびスキーマ »を参照してください。
📐 RouteConfig
withRoute は Partial<RouteConfig> を受け取るため、先行するコンポーザーとマージできます。省略時は protocol: 'http' を補い、WebSocket ルートでは method: 'GET' を補います。HTTP ルートでは method、path、handler を指定する必要があります。withRoute が HTTP メソッドのデフォルトを作ることはありません。保存されるルートには、以下の必須フィールドがあります。
interface RouteConfig {
protocol: 'http' | 'ws';
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
path: string;
validators?: Validator[] | WsValidator[];
schema?: SchemaDefinition; // レガシー — 通常は withSchema の withNextRoute により適用
openapi?: OpenAPIOperation; // 通常は withSchema の withNextRoute により適用
handler:
| AsyncRouteHandler
| RouteHandler
| AsyncWsRouteHandler
| WsRouteHandler;
}
プロトコル
| プロトコル | 目的 | 最終ハンドラー出力 |
|---|---|---|
http | REST API エンドポイント | ターミネーター後のプレーンな JSON データ、または { statusCode, data } |
ws | リアルタイム WebSocket 接続 | RxJS Subject(ブロックチェーンまたは直接ハンドラー経由) |
Result ベースのチェーンでは、中間ステップは Result 値を返します。ターミネーター(orThrow またはカスタムの *Terminator)が、defService がクライアントへ送信する値を生成します。
HTTP レスポンスの処理
defService は最終ハンドラー結果を次のように処理します。
- 結果に
statusCodeプロパティがある場合 —res.status(statusCode).json(data)(削除時の{ statusCode: 204 }など) - それ以外 — プレーンなレスポンスデータとして
res.json(result)
defService はルーターに express.json() も追加し、すべての service.middleware エントリーを適用します。
メソッド
| 動詞 | 目的 |
|---|---|
GET | リソースを読み取り・取得する |
POST | 新しいリソースを作成する |
PUT | 置換または完全更新する |
PATCH | 部分的に更新する |
DELETE | 削除する |
パス
Express 形式のテンプレートです。コレクションには複数形の名詞(/identities)、操作にはサブパス(/identities/:identityId/lock)を使用します。HTTP ルートでは動的部分が params.requestParams になります。
✅ バリデーター
すべてのハンドラー処理の前に実行される非同期フックです。HTTP では同じペイロードオブジェクト(RouteHandlerPayload)、WebSocket では WsRouteHandlerPayload を受け取ります。
import { primitives } from '@nodeblocks/backend-sdk';
export const validateEmail: primitives.Validator = async ({ params }) => {
if (!params.requestBody?.email?.includes('@')) {
throw new primitives.NodeblocksError(400, 'Invalid email', 'validateEmail');
}
};
リクエストを拒否する慣用的な方法は、throw new NodeblocksError(status, message) です。HTTP バリデーターは Promise<void> を返す必要があり、WebSocket バリデーターは void または Promise<void> を返せます。
🔗 ハンドラーチェーン
ルートは、合成されたハンドラーチェーンへビジネスロジックを委譲します。
- ブロックまたはハンドラー — ビジネスロジックを実行します(中間ステップは
Resultを返します)。 - ターミネーター — ブロックルートには
lift(orThrow(...))、レガシーハンドラールートにはlift(*Terminator)を使用します。
ブロックベースのルート(推奨)
新しいルート(identity、authentication、organization、product)には、applyPayloadArgs を使ったパラメーター駆動ブロックを推奨します。
import { blocks, primitives, validators } from '@nodeblocks/backend-sdk';
const { getIdentityById, normalizeIdentity } = blocks;
const { compose, flatMapAsync, withRoute, lift, applyPayloadArgs, orThrow } =
primitives;
const { isAuthenticated, checkIdentityType } = validators;
export const getIdentityRoute = withRoute({
method: 'GET',
path: '/identities/:identityId',
validators: [isAuthenticated(), checkIdentityType(['admin'])],
handler: compose(
applyPayloadArgs(
getIdentityById,
[
['context', 'db', 'identities'],
['params', 'requestParams', 'identityId'],
],
'rawIdentity'
),
flatMapAsync(
applyPayloadArgs(
normalizeIdentity,
[['context', 'data', 'rawIdentity']],
'identity'
)
),
lift(orThrow([], [['context', 'data', 'identity']]))
),
});
型付けされたブロックエラーがあるルートでは、orThrow でマッピングします。
import { blocks } from '@nodeblocks/backend-sdk';
const { OrganizationBlockError } = blocks;
lift(
orThrow(
[[OrganizationBlockError, 500]],
[['context', 'data', 'organization']]
)
);
ブロックの完全なパターンはブロック »を参照してください。
ハンドラーベースのルート(レガシー)
category、order、attributes、invitation、および chat の一部は、カスタムターミネーターとともに handlers.* を直接合成します。
import { handlers, primitives, validators } from '@nodeblocks/backend-sdk';
const { createCategory, getCategoryById, normalizeCategoryTerminator } = handlers;
const { compose, flatMapAsync, withRoute, lift, withLogging } = primitives;
const { isAuthenticated, checkIdentityType } = validators;
export const createCategoryRoute = withRoute({
method: 'POST',
path: '/categories',
validators: [isAuthenticated(), checkIdentityType(['admin'])],
handler: compose(
withLogging(createCategory),
flatMapAsync(withLogging(getCategoryById)),
lift(withLogging(normalizeCategoryTerminator))
),
});
レガシーハンドラーのパターンと移行に関する注意はハンドラー »を参照してください。
合成パターン
ブロックチェーン:
handler: compose(
applyPayloadArgs(block1, [/* paths */], 'result1'),
flatMapAsync(applyPayloadArgs(block2, [/* paths */], 'result2')),
lift(orThrow([[BlockError1, 404], [BlockError2, 500]], [['context', 'data', 'result2']]))
)
ハンドラーチェーン:
handler: compose(
handler1,
flatMapAsync(handler2),
lift(terminatorHandler)
)
ルートラッパー
ハンドラーステップの周囲に適用する一般的なコンビネーターです。
withLogging— 秘匿化を伴う構造化ログwithPagination—findクエリの自動ページネーションwithPaginatedProperty— ネストした配列プロパティのページネーションwithSoftDelete— 読み書き時の透過的な論理削除フィルタリング
🔌 WebSocket ルート
WebSocket ルートには protocol: 'ws' を使用します。WebSocketServer を defService の第 2 引数として渡します。
defService(chatFeature, wss);
defService は接続時に params.requestQuery(WebSocket URL から解析)を注入します。
SDK パターン:ブロック合成チェーン
SDK の本番 WebSocket ルート(streamChatMessagesRoute)は、context.data から RxJS Subject を取り出して終わるブロック合成チェーンを使います。
import { blocks, primitives } from '@nodeblocks/backend-sdk';
const { streamChatMessages, normalizeChatMessageStream, ChatMessageBadRequestError, ChatMessageUnknownError } = blocks;
const { compose, applyPayloadArgs, flatMapAsync, lift, orThrow, withLogging, withRoute } = primitives;
export const streamChatMessagesRoute = withRoute({
protocol: 'ws',
path: '/messages/listen',
handler: compose(
withLogging(
applyPayloadArgs(
streamChatMessages,
[
['context', 'db', 'chatMessages'],
['params', 'requestQuery', 'channelId'],
],
'streamSubject'
)
),
flatMapAsync(
withLogging(
applyPayloadArgs(
normalizeChatMessageStream,
[
['context', 'fileStorageDriver'],
['context', 'data', 'streamSubject'],
],
'normalizedStreamSubject'
)
)
),
lift(
withLogging(
orThrow(
[
[ChatMessageBadRequestError, 400],
[ChatMessageUnknownError, 500],
],
[['context', 'data', 'normalizedStreamSubject']]
)
)
)
),
});
カスタム WebSocket ハンドラー
SDK は、RxJS Subject を直接返すハンドラー向けに WsRouteHandler および AsyncWsRouteHandler 型も定義しています。ブロックチェーンパターン以外のカスタム実装にはこれらを使用してください。
完全な WebSocket パターンはWebSocket サービスの作成 »を参照してください。
WebSocket と HTTP の比較
| 観点 | HTTP ルート | WebSocket ルート |
|---|---|---|
| プロトコル | 'http'(デフォルト) | 'ws' |
| メソッド | 必須(GET、POST など) | 内部的には GET がデフォルト |
| ハンドラー出力 | プレーンなデータまたは { statusCode, data } | RxJS Subject |
| 通信 | リクエスト・レスポンス | 双方向ストリーミング |
| マウント | defService(feature) | defService(feature, wss) |
📐 推奨事項
- パスの意味論 — リソースには名詞(
/identities)、操作にはサブパス(/identities/:id/lock)を使用します。 - 完全な Result チェーン —
Resultベースのチェーンはターミネーター(orThrowまたは*Terminator)で終えます。 - ブロックを優先する — 新しいルートにはパラメーター駆動ブロックを使い、ハンドラーはレガシードメインだけに使用します。
- ロジックを小さく保つ — 単一の巨大な処理より、複数のコンポーザブルを優先します。
- バリデーターを先に置く — DB 呼び出しの前に軽量な検査を行います。
- ステートレスにする — ルートはグローバル状態を変更せず、すべてを
payload経由で受け取るようにします。
📦 SDK ルートモジュール
ルートは、src/routes/index.ts に対応する SDK の routes 名前空間でドメイン別に整理されています。
| モジュール | 説明 | 参照 |
|---|---|---|
| Address | 住所検索エンドポイント | SDK のみ — ドキュメントは未作成 |
| Attributes | 属性管理ルート | Attribute ルート » |
| Authentication | ログイン、ログアウト、MFA、トークン | Authentication ルート » |
| Category | カテゴリー CRUD ルート | Category ルート » |
| Chat | チャネル、メッセージ、購読(WS を含む) | Chat ルート » |
| Identity | Identity ライフサイクルルート | Identity ルート » |
| Invitation | 招待管理ルート | Invitation ルート » |
| Location | Location 管理ルート | Location ルート » |
| Notification | 通知エンドポイント | SDK のみ — ドキュメントは未作成 |
| OAuth | Google、Twitter、LINE OAuth ルート | OAuth ルート » |
| Order | 注文管理ルート | Order ルート » |
| Organization | 組織ルート | Organization ルート » |
| Product | 商品管理ルート | Product ルート » |
| Profile | プロフィールとソーシャルルート | Profile ルート » |
➡️ 次へ
ルートをスキーマと合成する方法は機能 »、バリデーションはスキーマ »、ハンドラーチェーンのパターンはブロック »およびハンドラー »を参照してください。