Skip to content

tRPC

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

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

新しい 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
    パラメータ デフォルト 説明
    name 必須 string - The name of the API (required). Used to generate class names and file paths.
    computeType string ServerlessApiGatewayRestApi The type of compute to use to deploy this API. Choose between ServerlessApiGatewayRestApi (default) or ServerlessApiGatewayHttpApi.
    auth string IAM The method used to authenticate with your API. Choose between IAM (default), Cognito or None.
    directory string packages The directory to store the application in.
    iacProvider string Inherit The preferred IaC provider. By default this is inherited from your initial selection.

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

    • Directorysrc
      • init.ts バックエンド tRPC の初期化
      • router.ts tRPC ルーター定義(Lambda ハンドラーの API エントリーポイント)
      • Directoryschema Zod を使用したスキーマ定義
        • echo.ts 「echo」プロシージャの入力と出力の例
      • 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 プロジェクト設定とビルドターゲット

    このジェネレータは選択した iacProvider に基づいてInfrastructure as Codeを生成するため、packages/common に関連するCDKコンストラクトまたはTerraformモジュールを含むプロジェクトを作成します。

    共通のInfrastructure as Codeプロジェクトは以下の構造を持ちます:

    • Directorypackages/common/constructs
      • Directorysrc
        • Directoryapp/ プロジェクト/ジェネレータ固有のインフラストラクチャ用コンストラクト
        • Directorycore/ app 内のコンストラクトで再利用される汎用コンストラクト
        • index.ts app からコンストラクトをエクスポートするエントリーポイント
      • project.json プロジェクトのビルドターゲットと設定

    APIをデプロイするために、以下のファイルが生成されます:

    • Directorypackages/common/constructs/src
      • Directoryapp
        • Directoryapis
          • <project-name>.ts APIをデプロイするためのCDKコンストラクト
      • Directorycore
        • Directoryapi
          • http-api.ts HTTP APIをデプロイするCDKコンストラクト(HTTP APIをデプロイする選択をした場合)
          • rest-api.ts REST APIをデプロイするCDKコンストラクト(REST APIをデプロイする選択をした場合)
          • utils.ts APIコンストラクト用のユーティリティ

    大まかに言うと、tRPC API はリクエストを特定のプロシージャに委譲するルーターで構成されます。各プロシージャには Zod スキーマで定義された入力と出力があります。

    src/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 公式ドキュメント を参照してください。

    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 }));

    上記の分解:

    • publicProceduresrc/middleware に設定されたミドルウェアを含む API の公開メソッドを定義
    • input は操作の期待される入力を定義する Zod スキーマを受け入れる
    • output は操作の期待される出力を定義する Zod スキーマを受け入れる
    • query は API の実装を定義する関数を受け入れる。opts には操作に渡された入力と、opts.ctx でミドルウェアが設定したコンテキストが含まれる

    query の使用は操作が非変異的であることを示します。データ取得メソッドの定義に使用します。変異操作には mutation メソッドを使用します。

    新しいプロシージャを追加する場合は、src/router.ts のルーターに登録してください。

    実装では、TRPCError をスローしてクライアントにエラーレスポンスを返せます:

    throw new TRPCError({
    code: 'NOT_FOUND',
    message: 'The requested resource could not be found',
    });

    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 で設定され、opts.ctx.logger 経由で実装内でアクセス可能です:

    export const echo = publicProcedure
    .input(...)
    .output(...)
    .query(async (opts) => {
    opts.ctx.logger.info('Operation called with input', opts.input);
    return ...;
    });

    詳細は AWS Lambda Powertools Logger ドキュメント を参照。

    AWS Lambda Powertools メトリクスは src/middleware/metrics.ts で設定され、opts.ctx.metrics 経由でアクセス可能です:

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

    詳細は AWS Lambda Powertools Metrics ドキュメント を参照。

    AWS Lambda Powertools トレーサーは src/middleware/tracer.ts で設定され、opts.ctx.tracer 経由でアクセス可能です:

    export const echo = publicProcedure
    .input(...)
    .output(...)
    .query(async (opts) => {
    const subSegment = opts.ctx.tracer.getSegment()!.addNewSubsegment('MyAlgorithm');
    // ... トレース対象のアルゴリズムロジック
    subSegment.close();
    return ...;
    });

    詳細は AWS Lambda Powertools Tracer ドキュメント を参照。

    コンテキストに追加の値を提供するミドルウェアを実装できます。

    例: src/middleware/identity.ts に API 呼び出し元の詳細を抽出するミドルウェアを実装:

    この例は authIAM に設定されていることを想定。Cognito 認証の場合、event から関連クレームを抽出可能

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

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

    ミドルウェア実装の構造:

    export const createIdentityPlugin = () => {
    const t = initTRPC.context<...>().create();
    return t.procedure.use(async (opts) => {
    // プロシージャ実行前のロジック
    const response = await opts.next(...);
    // プロシージャ実行後のロジック
    return response;
    });
    };
    // REST API 向け実装(原文のコードを保持)

    tRPC API ジェネレータは選択した iacProvider に基づき CDK または Terraform の IaC を生成します。

    CDK コンストラクトの使用例:

    // CDK 実装例(原文のコードを保持)

    REST/HTTP API CDKコンストラクトは、各オペレーションの統合を定義するための型安全なインターフェースを提供するように設定されています。

    CDKコンストラクトは、以下で説明する完全な型安全な統合サポートを提供します。

    静的メソッドdefaultIntegrationsを使用して、各オペレーションに個別のAWS Lambda関数を定義するデフォルトパターンを利用できます:

    new MyApi(this, 'MyApi', {
    integrations: MyApi.defaultIntegrations(this).build(),
    });

    APIコンストラクトのintegrationsプロパティを通じて、型安全な方法で基盤となるAWS Lambda関数にアクセスできます。例えば、APIがsayHelloというオペレーションを定義している場合、この関数に権限を追加するには次のようにします:

    const api = new MyApi(this, 'MyApi', {
    integrations: MyApi.defaultIntegrations(this).build(),
    });
    // sayHelloはAPIで定義されたオペレーションに型付けされます
    api.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({
    effect: Effect.ALLOW,
    actions: [...],
    resources: [...],
    }));

    デフォルトオプションのカスタマイズ

    Section titled “デフォルトオプションのカスタマイズ”

    デフォルト統合で作成されるLambda関数のオプションをカスタマイズするには、withDefaultOptionsメソッドを使用します。例えば、すべてのLambda関数をVPC内に配置する場合:

    const vpc = new Vpc(this, 'Vpc', ...);
    new MyApi(this, 'MyApi', {
    integrations: MyApi.defaultIntegrations(this)
    .withDefaultOptions({
    vpc,
    })
    .build(),
    });

    withOverridesメソッドを使用して特定のオペレーションの統合をオーバーライドできます。各オーバーライドは、HTTPまたはREST APIに適したCDK統合コンストラクトに型付けされたintegrationプロパティを指定する必要があります。例として、getDocumentation APIを外部サイトのドキュメントにポイントする場合:

    new MyApi(this, 'MyApi', {
    integrations: MyApi.defaultIntegrations(this)
    .withOverrides({
    getDocumentation: {
    integration: new HttpIntegration('https://example.com/documentation'),
    },
    })
    .build(),
    });

    オーバーライドされた統合は、api.integrations.getDocumentationでアクセスした際にhandlerプロパティを持たないことに注意してください。

    追加プロパティを統合に追加することで、他のタイプの統合を抽象化しつつ型安全性を維持できます。例えば、REST API用にS3統合を作成し、後で特定のオペレーションでバケットを参照する場合:

    const storageBucket = new Bucket(this, 'Bucket', { ... });
    const apiGatewayRole = new Role(this, 'ApiGatewayS3Role', {
    assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),
    });
    storageBucket.grantRead(apiGatewayRole);
    const api = new MyApi(this, 'MyApi', {
    integrations: MyApi.defaultIntegrations(this)
    .withOverrides({
    getFile: {
    bucket: storageBucket,
    integration: new AwsIntegration({
    service: 's3',
    integrationHttpMethod: 'GET',
    path: `${storageBucket.bucketName}/{fileName}`,
    options: {
    credentialsRole: apiGatewayRole,
    requestParameters: {
    'integration.request.path.fileName': 'method.request.querystring.fileName',
    },
    integrationResponses: [{ statusCode: '200' }],
    },
    }),
    options: {
    requestParameters: {
    'method.request.querystring.fileName': true,
    },
    methodResponses: [{
    statusCode: '200',
    }],
    }
    },
    })
    .build(),
    });
    // 後で別ファイルで、定義したbucketプロパティに
    // 型安全にアクセスできます
    api.integrations.getFile.bucket.grantRead(...);

    オーソライザーのオーバーライド

    Section titled “オーソライザーのオーバーライド”

    統合にoptionsを指定して、特定のメソッドオプション(オーソライザーなど)をオーバーライドできます。例として、getDocumentationオペレーションにCognito認証を使用する場合:

    new MyApi(this, 'MyApi', {
    integrations: MyApi.defaultIntegrations(this)
    .withOverrides({
    getDocumentation: {
    integration: new HttpIntegration('https://example.com/documentation'),
    options: {
    authorizer: new CognitoUserPoolsAuthorizer(...) // REST用、HTTP APIの場合はHttpUserPoolAuthorizer
    }
    },
    })
    .build(),
    });

    必要に応じて、デフォルト統合を使用せずに各オペレーションに直接統合を指定できます。例えば、オペレーションごとに異なる統合タイプを使用する場合や、新しいオペレーション追加時に型エラーを受け取りたい場合に有用です:

    new MyApi(this, 'MyApi', {
    integrations: {
    sayHello: {
    integration: new LambdaIntegration(...),
    },
    getDocumentation: {
    integration: new HttpIntegration(...),
    },
    },
    });

    すべてのAPIリクエストを処理する単一のLambda関数をデプロイしたい場合、APIのdefaultIntegrationsメソッドを編集して、統合ごとではなく単一の関数を作成できます:

    packages/common/constructs/src/app/apis/my-api.ts
    export class MyApi<...> extends ... {
    public static defaultIntegrations = (scope: Construct) => {
    const router = new Function(scope, 'RouterHandler', { ... });
    return IntegrationBuilder.rest({
    ...
    defaultIntegrationOptions: {},
    buildDefaultIntegration: (op) => {
    return {
    // すべての統合で同じルータLambdaハンドラを参照
    integration: new LambdaIntegration(router),
    };
    },
    });
    };
    }

    必要に応じて、router関数をdefaultIntegrationsのパラメータとして定義するなど、他の方法でコードを修正することもできます。

    IAM 認証を選択した場合のアクセス権付与:

    api.grantInvokeAccess(myIdentityPool.authenticatedRole);

    ジェネレーターは自動的に Rolldown を使用する bundle ターゲットを設定します。このターゲットはデプロイメントパッケージの作成に使用されます:

    Terminal window
    pnpm nx run <project-name>:bundle

    Rolldownの設定はrolldown.config.tsに記述され、生成するバンドルごとにエントリを定義します。Rolldownは定義された複数のバンドルを並行して作成する処理を管理します。

    ローカルサーバーの起動:

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

    エントリーポイントは src/local-server.ts です。変更は自動的にリロードされます。

    型安全なクライアントを作成可能:

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

    React ウェブサイトからの呼び出しには API 接続 ジェネレータの使用を検討してください。

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