🚦 ルート
ルートは、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, '無効なメールアドレスです');
}
};
ハンドラー
ルートはビジネスロジックをハンドラーチェーンに委任します—リクエストを処理してレスポンスをフォーマットする構成された関数。すべてのハンドラーチェーンは以下のパターンに従います:
- ハンドラー - ビジネスロジックを実行(データベース操作、検証、変換)
- ターミネーターハンドラー - HTTPレスポンスをフォーマットする最終ハンドラー
handler: compose(
handler1, // Result<RouteHandlerPayload, Error>を返す
flatMapAsync(handler2), // Result<RouteHandlerPayload, Error>を返す
lift(terminatorHandler) // Resultを処理し、フォーマットされたレスポンスを返す
)
ハンドラーには署名、mergeData
、Resultパターン、ターミネーター要件をカバーする専用の説明があります。➜ ハンドラーの説明
2️⃣ 良いプラクティス
- パスセマンティクス – リソースには名詞(
/users
)、アクションには動詞(/users/:id/lock
) - 完全なハンドラーチェーン – 常に最終レスポンスをフォーマットするターミネーターハンドラーで終了
- ハンドラーを小さく保つ – 単一のモノリスより複数の構成可能なものを優先
- バリデーターを最初に – DB呼び出し前に軽量チェックを実行
- ステートレス – ルートはグローバル状態を変更すべきではない;すべては
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
});
➡️ 次へ
フィーチャー »について学習して、ルートとスキーマを再利用可能なユニットに構成する方法を理解しましょう