Skip to content

tRPC

tRPCは、エンドツーエンドの型安全性を備えたTypeScript API構築フレームワークです。tRPCを使用すると、API操作の入力と出力の変更が即座にクライアントコードに反映され、プロジェクトの再ビルドなしにIDE上で可視化されます。

tRPC APIジェネレータは、AWS CDKインフラストラクチャが設定された新しいtRPC APIを作成します。生成されたバックエンドはサーバーレスデプロイメントにAWS Lambdaを使用し、Zodによるスキーマ検証を実装しています。また、ロギング・AWS X-Rayトレーシング・CloudWatchメトリクスを含むオブザーバビリティのためにAWS Lambda Powertoolsを設定します。

使用方法

tRPC APIの生成

新しいtRPC APIは2つの方法で生成できます:

  1. インストール Nx Console VSCode Plugin まだインストールしていない場合
  2. VSCodeでNxコンソールを開く
  3. クリック Generate (UI) "Common Nx Commands"セクションで
  4. 検索 @aws/nx-plugin - ts#trpc-api
  5. 必須パラメータを入力
    • クリック Generate

    オプション

    パラメータ デフォルト 説明
    apiName 必須 string - The name of the API (required). Used to generate class names and file paths.
    directory string packages The directory to store the application in.

    ジェネレータの出力

    ジェネレータは<directory>/<api-name>ディレクトリに以下のプロジェクト構造を作成します:

    • Directoryschema
      • Directorysrc
        • index.ts スキーマエントリポイント
        • Directoryprocedures
          • echo.ts 「echo」プロシージャの共有スキーマ定義(Zod使用)
      • tsconfig.json TypeScript設定
      • project.json プロジェクト設定とビルドターゲット
    • Directorybackend
      • Directorysrc
        • init.ts バックエンドtRPC初期化
        • router.ts tRPCルータ定義(LambdaハンドラAPIエントリポイント)
        • Directoryprocedures APIが公開するプロシージャ(操作)
          • echo.ts サンプルプロシージャ
        • Directorymiddleware
          • error.ts エラーハンドリング用ミドルウェア
          • logger.ts AWS Powertoolsロギング設定用ミドルウェア
          • tracer.ts AWS Powertoolsトレーシング設定用ミドルウェア
          • metrics.ts AWS Powertoolsメトリクス設定用ミドルウェア
        • local-server.ts ローカル開発サーバ用tRPCスタンドアロンアダプタ
        • Directoryclient
          • index.ts 機械間API呼び出し用型安全クライアント
      • tsconfig.json TypeScript設定
      • project.json プロジェクト設定とビルドターゲット

    ジェネレータはまた、APIデプロイに使用できるCDKコンストラクトをpackages/common/constructsディレクトリに作成します。

    tRPC APIの実装

    上記のように、tRPC APIはschemabackendの2つの主要コンポーネントで構成され、ワークスペース内で個別のパッケージとして定義されます。

    スキーマ

    schemaパッケージはクライアントとサーバーコード間で共有される型を定義します。これらの型はTypeScriptファーストのスキーマ宣言・検証ライブラリであるZodを使用して定義されます。

    サンプルスキーマの例:

    import { z } from 'zod';
    // スキーマ定義
    export const UserSchema = z.object({
    name: z.string(),
    height: z.number(),
    dateOfBirth: z.string().datetime(),
    });
    // 対応するTypeScript型
    export type User = z.TypeOf<typeof UserSchema>;

    上記スキーマの場合、User型は以下のTypeScriptと同等です:

    interface User {
    name: string;
    height: number;
    dateOfBirth: string;
    }

    スキーマはサーバーとクライアントコードの両方で共有され、APIで使用される構造を変更する際の単一の更新ポイントを提供します。

    スキーマは実行時にtRPC APIによって自動検証され、バックエンドで独自の検証ロジックを作成する手間を省きます。

    Zodはスキーマの結合や派生のための.merge.pick.omitなどの強力なユーティリティを提供します。詳細はZod公式ドキュメントを参照してください。

    バックエンド

    backendフォルダにはAPI実装が含まれており、API操作とその入力・出力・実装を定義します。

    APIのエントリポイントはsrc/router.tsにあります。このファイルには、呼び出される操作に基づいてリクエストを「プロシージャ」にルーティングするLambdaハンドラが含まれます。各プロシージャは入力・出力・実装を定義します。

    生成されるサンプルルータにはechoという単一の操作が含まれます:

    import { echo } from './procedures/echo.js';
    export const appRouter = router({
    echo,
    });

    サンプルのechoプロシージャはsrc/procedures/echo.tsに生成されます:

    export const echo = publicProcedure
    .input(EchoInputSchema)
    .output(EchoOutputSchema)
    .query((opts) => ({ result: opts.input.message }));

    上記の分解説明:

    • publicProcedureはAPIの公開メソッドを定義し、src/middlewareに設定されたミドルウェアを含みます。このミドルウェアにはロギング・トレーシング・メトリクスのためのAWS Lambda Powertools統合が含まれます
    • inputは操作の期待入力を定義するZodスキーマを受け入れます。この操作へのリクエストは自動的にこのスキーマに対して検証されます
    • outputは操作の期待出力を定義するZodスキーマを受け入れます。スキーマに準拠しない出力を返す場合、実装で型エラーが発生します
    • queryはAPIの実装を定義する関数を受け入れます。この実装はoptsを受け取り、操作に渡されたinputopts.ctxで利用可能なミドルウェアが設定したコンテキストを含みます。queryに渡す関数はoutputスキーマに準拠する出力を返す必要があります

    queryの使用は操作が非変更的であることを示します。データ取得メソッドの定義に使用します。変更操作を実装する場合は、代わりにmutationメソッドを使用してください。

    新しい操作を追加する場合、src/router.tsのルータに登録する必要があります。

    tRPC APIのカスタマイズ

    エラー処理

    実装ではTRPCErrorをスローしてクライアントにエラー応答を返すことができます。エラータイプを示すcodeを受け入れます:

    throw new TRPCError({
    code: 'NOT_FOUND',
    message: '要求されたリソースが見つかりませんでした',
    });

    操作の整理

    APIが成長するにつれ、関連する操作をグループ化したい場合があります。

    ネストされたルータを使用して操作をグループ化できます:

    import { getUser } from './procedures/users/get.js';
    import { listUsers } from './procedures/users/list.js';
    const appRouter = router({
    users: router({
    get: getUser,
    list: listUsers,
    }),
    ...
    })

    クライアントはこの操作グループを受け取り、例えばlistUsers操作の呼び出しは以下のようになります:

    client.users.list.query();

    ロギング

    AWS Lambda Powertoolsロガーはsrc/middleware/logger.tsで設定され、API実装ではopts.ctx.loggerでアクセス可能です。CloudWatch Logsへのロギングや、構造化ログメッセージに含める追加値の制御に使用できます:

    export const echo = publicProcedure
    .input(...)
    .output(...)
    .query(async (opts) => {
    opts.ctx.logger.info('操作が入力値で呼び出されました', opts.input);
    return ...;
    });

    ロガーの詳細についてはAWS Lambda Powertools Loggerドキュメントを参照してください。

    メトリクス記録

    AWS Lambda Powertoolsメトリクスはsrc/middleware/metrics.tsで設定され、opts.ctx.metricsでアクセス可能です。AWS SDKをインポートせずにCloudWatchメトリクスを記録するために使用できます:

    export const echo = publicProcedure
    .input(...)
    .output(...)
    .query(async (opts) => {
    opts.ctx.metrics.addMetric('Invocations', 'Count', 1);
    return ...;
    });

    詳細はAWS Lambda Powertools Metricsドキュメントを参照してください。

    X-Rayトレーシングの微調整

    AWS Lambda Powertoolsトレーサーはsrc/middleware/tracer.tsで設定され、opts.ctx.tracerでアクセス可能です。APIリクエストのパフォーマンスとフローの詳細な可視化を提供するAWS X-Rayトレースの追加に使用できます:

    export const echo = publicProcedure
    .input(...)
    .output(...)
    .query(async (opts) => {
    const subSegment = opts.ctx.tracer.getSegment()!.addNewSubsegment('MyAlgorithm');
    // ... キャプチャするアルゴリズムロジック
    subSegment.close();
    return ...;
    });

    詳細はAWS Lambda Powertools Tracerドキュメントを参照してください。

    カスタムミドルウェアの実装

    ミドルウェアを実装することで、プロシージャに提供されるコンテキストに追加値を設定できます。

    例として、API呼び出し元ユーザーの詳細を抽出するミドルウェアをsrc/middleware/identity.tsに実装します。

    まずコンテキストに追加する内容を定義:

    export interface IIdentityContext {
    identity?: {
    sub: string;
    username: string;
    };
    }

    次にミドルウェアを実装:

    export const createIdentityPlugin = () => {
    const t = initTRPC.context<IIdentityContext>().create();
    return t.procedure.use(async (opts) => {
    // プロシージャ実行前のロジック
    const response = await opts.next(...);
    // プロシージャ実行後のロジック
    return response;
    });
    };

    Cognitoユーザーの詳細を抽出する実装例:

    import { CognitoIdentityProvider } from '@aws-sdk/client-cognito-identity-provider';
    export const createIdentityPlugin = () => {
    const t = initTRPC.context<IIdentityContext>().create();
    const cognito = new CognitoIdentityProvider();
    return t.procedure.use(async (opts) => {
    const cognitoIdentity = opts.ctx.event.requestContext?.authorizer?.iam
    ?.cognitoIdentity as unknown as
    | {
    amr: string[];
    }
    | undefined;
    const sub = (cognitoIdentity?.amr ?? [])
    .flatMap((s) => (s.includes(':CognitoSignIn:') ? [s] : []))
    .map((s) => {
    const parts = s.split(':');
    return parts[parts.length - 1];
    })?.[0];
    if (!sub) {
    throw new TRPCError({
    code: 'FORBIDDEN',
    message: `呼び出しユーザーを特定できません`,
    });
    }
    const { Users } = await cognito.listUsers({
    UserPoolId: process.env.USER_POOL_ID!,
    Limit: 1,
    Filter: `sub="${sub}"`,
    });
    if (!Users || Users.length !== 1) {
    throw new TRPCError({
    code: 'FORBIDDEN',
    message: `subjectId ${sub}のユーザーが見つかりません`,
    });
    }
    // コンテキストにidentityを提供
    return await opts.next({
    ctx: {
    ...opts.ctx,
    identity: {
    sub,
    username: Users[0].Username!,
    },
    },
    });
    });
    };

    tRPC APIのデプロイ

    tRPCバックエンドジェネレータはcommon/constructsフォルダにAPIデプロイ用CDKコンストラクトを生成します。CDKアプリケーションで使用できます:

    import { MyApi } from ':my-scope/common-constructs`;
    export class ExampleStack extends Stack {
    constructor(scope: Construct, id: string) {
    // スタックにAPIを追加
    const api = new MyApi(this, 'MyApi');
    }
    }

    この設定では、AWS API Gateway HTTP API、ビジネスロジック用AWS Lambda関数、IAM認証を含むAPIインフラストラクチャが構成されます。

    アクセス権限付与

    grantInvokeAccessメソッドを使用してAPIへのアクセス権を付与できます。例: Cognito認証ユーザーにAPIアクセスを許可:

    api.grantInvokeAccess(myIdentityPool.authenticatedRole);

    ローカルtRPCサーバー

    serveターゲットを使用してAPIのローカルサーバーを実行できます:

    Terminal window
    pnpm nx run @my-scope/my-api-backend:serve

    ローカルサーバーのエントリポイントはsrc/local-server.tsです。

    tRPC APIの呼び出し

    型安全な方法でAPIを呼び出すtRPCクライアントを作成できます。別のバックエンドからAPIを呼び出す場合、src/client/index.tsのクライアントを使用できます:

    import { createMyApiClient } from ':my-scope/my-api-backend';
    const client = createMyApiClient({ url: 'https://my-api-url.example.com/' });
    await client.echo.query({ message: 'Hello world!' });

    ReactウェブサイトからAPIを呼び出す場合は、API接続ジェネレータを使用してクライアントを構成することを検討してください。

    追加情報

    tRPCの詳細についてはtRPC公式ドキュメントを参照してください。