メインコンテンツまでスキップ
バージョン: 0.4.2

🚦 ルート

ルートは、HTTPメソッド、パス、バリデーター、ビジネスロジックをまとめます。NodeblocksはwithRouteヘルパーを提供し、メタデータを保存してサービスルーターが生成されるときにExpressに自動的にプラグインします。


1️⃣ 構造

src/routes/user.ts
import {compose, flatMapAsync, withRoute, lift} from '../primitives';
import {createUser, getUserById, normalizeUserTerminator} from '../handlers';

export const createUserRoute = withRoute({
method: 'POST',
path: '/users',
validators: [verifyAuthentication(getBearerTokenInfo)], // バリデーター関数のオプション配列
handler: compose(
createUser, // ハンドラー: DBに書き込み
flatMapAsync(getUserById), // ハンドラー: 作成されたエンティティを読み返し
lift(normalizeUserTerminator) // ターミネーターハンドラー: 最終HTTPレスポンスをフォーマット
),
});

以下はwithRouteが返す最小限の構造です。

interface Route {
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
path: string; // Express形式のパス、:paramsをサポート
validators?: Validator[]; // ハンドラーチェーンの**前**に実行されるオプションのリスト
handler: RouteHandler; // 構成可能な関数チェーン
}

メソッド

REST規約に従う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, '無効なメールアドレスです');
}
};

ハンドラー

ルートはビジネスロジックをハンドラーチェーンに委任します—リクエストを処理してレスポンスをフォーマットする構成された関数。すべてのハンドラーチェーンは以下のパターンに従います:

  1. ハンドラー - ビジネスロジックを実行(データベース操作、検証、変換)
  2. ターミネーターハンドラー - HTTPレスポンスをフォーマットする最終ハンドラー
handler: compose(
handler1, // Result<RouteHandlerPayload, Error>を返す
flatMapAsync(handler2), // Result<RouteHandlerPayload, Error>を返す
lift(terminatorHandler) // Resultを処理し、フォーマットされたレスポンスを返す
)

ハンドラーには署名、mergeData、Resultパターン、ターミネーター要件をカバーする専用の説明があります。➜ ハンドラーの説明


2️⃣ 良いプラクティス

  1. パスセマンティクス – リソースには名詞(/users)、アクションには動詞(/users/:id/lock
  2. 完全なハンドラーチェーン – 常に最終レスポンスをフォーマットするターミネーターハンドラーで終了
  3. ハンドラーを小さく保つ – 単一のモノリスより複数の構成可能なものを優先
  4. バリデーターを最初に – DB呼び出し前に軽量チェックを実行
  5. ステートレス – ルートはグローバル状態を変更すべきではない;すべてはpayloadを介して到着

ハンドラーチェーンパターン

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

3️⃣ バリデーターの追加

import {withRoute, compose} from '../primitives';
import {NodeblocksError} from '../primitives/error';
import {ok, err} from 'neverthrow';
import {mergeData} from '../handlers';

// 常にアクセスをブロックするカスタムバリデーター
const youShallNotPass = () => {
throw new NodeblocksError(400, '通してはならぬ!');
};

// 秘密データを取得するハンドラー
const getSecretData = async (payload) => {
return ok(mergeData(payload, { hiddenSecret: '🧱🧱🎉🧱🧱' }));
};

// レスポンスをフォーマットするターミネーターハンドラー
const secretTerminator = (result) => {
if (result.isErr()) {
throw result.error;
}
const payload = result.value;
const { context } = payload;

return { secret: context.data.secret };
};

export const getSecretRoute = withRoute({
method: 'GET',
path: '/secret',
validators: [youShallNotPass],
handler: compose(
getSecretData, // ハンドラー: データを取得
lift(secretTerminator) // ターミネーター: 最終レスポンスをフォーマット
),
});

バリデーターは失敗時にNodeblocksErrorをスローし、これがエラーミドルウェアまでバブリングします。


🎯 ルートのベストプラクティス

1. RESTful設計

// 良い - RESTful規約に従う
export const getUserRoute = withRoute({
method: 'GET',
path: '/users/:id'
});

export const createUserRoute = withRoute({
method: 'POST',
path: '/users'
});

// 悪い - 非RESTfulパターン
export const getUserRoute = withRoute({
method: 'POST',
path: '/getUser'
});

2. 適切なエラーハンドリング

const validateUserInput: Validator = ({ params }) => {
if (!params.requestBody.email) {
throw new NodeblocksError(400, 'メールアドレスは必須です');
}

if (!params.requestBody.name) {
throw new NodeblocksError(400, '名前は必須です');
}
};

3. ハンドラーコンポジション

// 良い - 関心の分離
export const createUserRoute = withRoute({
method: 'POST',
path: '/users',
handler: compose(
validateUserData, // 検証
createUser, // 作成
sendWelcomeEmail, // 副作用
normalizeUserTerminator // レスポンス
)
});

// 悪い - すべてを1つのハンドラーに
export const createUserRoute = withRoute({
method: 'POST',
path: '/users',
handler: createUserAndDoEverything
});

➡️ 次へ

フィーチャー »について学習して、ルートとスキーマを再利用可能なユニットに構成する方法を理解しましょう