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 Metricsを含む可観測性のためにAWS Lambda Powertoolsをセットアップします。
tRPC APIの生成
Section titled “tRPC APIの生成”新しいtRPC APIは2つの方法で生成できます:
pnpm nx g @aws/nx-plugin:ts#api --framework=trpcyarn nx g @aws/nx-plugin:ts#api --framework=trpcnpx nx g @aws/nx-plugin:ts#api --framework=trpcbunx nx g @aws/nx-plugin:ts#api --framework=trpc変更されるファイルを確認するためにドライランを実行することもできます
pnpm nx g @aws/nx-plugin:ts#api --framework=trpc --dry-runyarn nx g @aws/nx-plugin:ts#api --framework=trpc --dry-runnpx nx g @aws/nx-plugin:ts#api --framework=trpc --dry-runbunx nx g @aws/nx-plugin:ts#api --framework=trpc --dry-run- インストール Nx Console VSCode Plugin まだインストールしていない場合
- VSCodeでNxコンソールを開く
- クリック
Generate (UI)"Common Nx Commands"セクションで - 検索
@aws/nx-plugin - ts#api - 必須パラメータを入力
- framework: trpc
- クリック
Generate
| パラメータ | 型 | デフォルト | 説明 |
|---|---|---|---|
| name 必須 | string | - | APIの名前(必須)。クラス名とファイルパスの生成に使用されます。 |
| framework | trpc | smithy | trpc | 使用するAPIフレームワーク。 |
| namespace | string | - | Smithy APIの名前空間(smithyフレームワークにのみ適用されます)。デフォルトはモノレポのスコープです |
| integrationPattern | isolated | shared | isolated | API用にAPI Gateway統合を生成する方法。isolated(デフォルト)またはsharedから選択します。 |
| auth | iam | cognito | custom | iam | APIの認証に使用する方法。iam(デフォルト)、cognito、customから選択します。 |
| directory | string | packages | アプリケーションを保存するディレクトリ。 |
| subDirectory | string | - | プロジェクトが配置されるサブディレクトリ。デフォルトではプロジェクト名になります。 |
| iac | inherit | cdk | terraform | inherit | 優先するIaCプロバイダー。デフォルトでは初期選択から継承されます。 |
| infra | rest-lambda | http-lambda | none | rest-lambda | このAPIをデプロイするために使用するインフラストラクチャのタイプ。 |
| preferInstallDependencies | boolean | true | ジェネレーター実行後に依存関係のインストールを優先するかどうか。複数のジェネレーターをバッチ処理する際にインストールを延期する場合は false に設定します(後続のジェネレーターが Nx プロジェクトグラフを計算できるよう、必要に応じてインストールは実行されます)。最後に一度だけインストールします。 |
ジェネレーター出力
Section titled “ジェネレーター出力”ジェネレーターは<directory>/<api-name>ディレクトリに以下のプロジェクト構造を作成します:
Directorysrc
- init.ts Backend tRPC initialisation
- handler.ts Lambda handler entrypoint
- router.ts tRPC router definition
Directoryschema Schema definitions using Zod
- echo.ts Example definitions for the input and output of the “echo” procedure
- z-async-iterable.ts Zod helper for subscriptions (REST API only)
Directoryprocedures Procedures (or operations) exposed by your API
- echo.ts Example procedure
Directorymiddleware
- error.ts Middleware for error handling
- logger.ts middleware for configuring AWS Powertools for Lambda logging
- tracer.ts middleware for configuring AWS Powertools for Lambda tracing
- metrics.ts middleware for configuring AWS Powertools for Lambda metrics
- local-server.ts tRPC standalone adapter entrypoint for local development server
Directoryclient
- index.ts Type-safe client for machine-to-machine API calls
- tsconfig.json TypeScript configuration
- package.json Project manifest defining the project’s package name and dependencies
- project.json Project configuration and build targets
インフラストラクチャ
Section titled “インフラストラクチャ”このジェネレーターは、選択した iac に基づいてインフラストラクチャをコードとして提供するため、関連する CDK コンストラクトまたは Terraform モジュールを含む packages/common にプロジェクトを作成します。
共通のインフラストラクチャコードプロジェクトは、次のように構成されています:
Directorypackages/common/constructs
Directorysrc
Directoryapp/ プロジェクト/ジェネレーター固有のインフラストラクチャ用のコンストラクト
- …
Directorycore/
app内のコンストラクトによって再利用される汎用コンストラクト- …
- index.ts
appからコンストラクトをエクスポートするエントリーポイント
- project.json プロジェクトのビルドターゲットと設定
Directorypackages/common/terraform
Directorysrc
Directoryapp/ プロジェクト/ジェネレーター固有のインフラストラクチャ用の Terraform モジュール
- …
Directorycore/
app内のモジュールによって再利用される汎用モジュール- …
- project.json プロジェクトのビルドターゲットと設定
APIをデプロイするために、以下のファイルが生成されます:
Directorypackages/common/constructs/src
Directoryapp
Directoryapis
- <project-name>.ts CDK construct for deploying your API
Directorycore
Directoryapi
- http-api.ts CDK construct for deploying an HTTP API (if you selected to deploy an HTTP API)
- rest-api.ts CDK construct for deploying a REST API (if you selected to deploy a REST API)
- utils.ts Utilities for the API constructs
Directorypackages/common/terraform/src
Directoryapp
Directoryapis
Directory<project-name>
- <project-name>.tf Module for deploying your API
Directorycore
Directoryapi
Directoryhttp-api
- http-api.tf Module for deploying an HTTP API (if you selected to deploy an HTTP API)
Directoryrest-api
- rest-api.tf Module for deploying a REST API (if you selected to deploy a REST API)
アーキテクチャ
Section titled “アーキテクチャ”デプロイされたアプリケーションは以下のアーキテクチャを持ちます:
REST APIには、API Gatewayステージの前にAWS WAFv2 Web ACLが含まれており、AWSマネージドのデフォルトルールセットが有効になっています。
HTTP APIは直接WAFをサポートしていません。WAF保護が必要な場合は、代わりにREST APIを選択するか、HTTP APIの前にCloudFrontディストリビューションを配置してください。
tRPC APIの実装
Section titled “tRPC APIの実装”高レベルでは、tRPC APIはリクエストを特定のプロシージャに委譲するルーターで構成されます。各プロシージャには、Zodスキーマとして定義された入力と出力があります。
src/schemaディレクトリには、クライアントとサーバーコード間で共有される型が含まれています。このパッケージでは、これらの型はTypeScriptファーストのスキーマ宣言および検証ライブラリであるZodを使用して定義されています。
スキーマの例は次のようになります:
import { z } from 'zod';
// Schema definitionexport const UserSchema = z.object({ name: z.string(), height: z.number(), dateOfBirth: z.string().datetime(),});
// Corresponding TypeScript typeexport type User = z.TypeOf<typeof UserSchema>;上記のスキーマを考えると、User型は次のTypeScriptと同等です:
interface User { name: string; height: number; dateOfBirth: string;}スキーマはサーバーとクライアントの両方のコードで共有されるため、APIで使用される構造を変更する際の単一の場所を提供します。
スキーマはtRPC APIによって実行時に自動的に検証されるため、バックエンドでカスタム検証ロジックを手作業で作成する必要がありません。
Zodは、.merge、.pick、.omitなどのスキーマを組み合わせたり派生させたりするための強力なユーティリティを提供します。詳細については、Zodドキュメントウェブサイトを参照してください。
ルーターとプロシージャ
Section titled “ルーターとプロシージャ”tRPCルーターはsrc/router.tsで定義され、すべてのプロシージャを登録します。各プロシージャは、期待される入力、出力、および実装を定義します。Lambdaハンドラーのエントリーポイントはsrc/handler.tsにあり、リクエストをルーターに転送します。
生成されたサンプルルーターには、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) => ({ message: opts.input.message }));上記を分解すると:
publicProcedureは、src/middlewareでセットアップされたミドルウェアを含む、API上のパブリックメソッドを定義します。このミドルウェアには、ロギング、トレーシング、メトリクスのためのAWS Lambda Powertools統合が含まれます。inputは、操作の期待される入力を定義するZodスキーマを受け入れます。この操作に送信されたリクエストは、このスキーマに対して自動的に検証されます。outputは、操作の期待される出力を定義するZodスキーマを受け入れます。スキーマに準拠しない出力を返すと、実装で型エラーが表示されます。queryは、APIの実装を定義する関数を受け入れます。この実装は、操作に渡されたinputを含むoptsと、ミドルウェアによってセットアップされた他のコンテキストをopts.ctxで受け取ります。queryに渡される関数は、outputスキーマに準拠する出力を返す必要があります。
実装を定義するためのqueryの使用は、操作が変更を伴わないことを示します。これを使用してデータを取得するメソッドを定義します。変更を伴う操作を実装するには、代わりにmutationメソッドを使用します。
新しいプロシージャを追加する場合は、src/router.tsのルーターに追加して登録してください。
サブスクリプション(ストリーミング)
Section titled “サブスクリプション(ストリーミング)”tRPCサブスクリプションを使用すると、Server-Sent Events (SSE)を使用してサーバーからクライアントにデータをストリーミングできます。コンピュートタイプとしてrest-lambdaを選択すると、ジェネレーターはストリーミングに必要なインフラストラクチャ、ストリーミングLambdaハンドラー、およびZodAsyncIterableスキーマヘルパーを自動的に構成します。
サブスクリプションプロシージャを定義するには、非同期ジェネレーター関数で.subscriptionメソッドを使用します。src/schema/z-async-iterable.tsのZodAsyncIterableヘルパーを使用して出力スキーマを定義します:
import { publicProcedure } from '../init.js';import { z } from 'zod';import { ZodAsyncIterable } from '../schema/z-async-iterable.js';
const InputSchema = z.object({ query: z.string() });const ChunkSchema = z.object({ text: z.string() });
export const myStream = publicProcedure .input(InputSchema) .output( ZodAsyncIterable({ yield: ChunkSchema, }), ) .subscription(async function* (opts) { // Yield data to the client as it becomes available for (const chunk of await getResults(opts.input.query)) { yield { text: chunk }; } });他のプロシージャと同様に、ルーターにサブスクリプションを登録します:
export const appRouter = router({ echo, myStream,});生成されたインフラストラクチャは、すべてのREST API操作に対してAPI GatewayでResponseTransferMode.STREAMを使用したストリーミングLambdaハンドラーを使用し、サブスクリプションが通常のクエリとミューテーションと並行して動作できるようにします。
tRPC APIのカスタマイズ
Section titled “tRPC APIのカスタマイズ”実装では、TRPCErrorをスローすることで、クライアントにエラーレスポンスを返すことができます。これらは、エラーのタイプを示すcodeを受け入れます。例えば:
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で構成されており、API実装ではopts.ctx.loggerを介してアクセスできます。これを使用してCloudWatch Logsにログを記録したり、すべての構造化ログメッセージに含める追加の値を制御したりできます。例えば:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { opts.ctx.logger.info('Operation called with input', opts.input);
return ...; });ロガーの詳細については、AWS Lambda Powertoolsロガードキュメントを参照してください。
メトリクスの記録
Section titled “メトリクスの記録”AWS Lambda Powertoolsメトリクスはsrc/middleware/metrics.tsで構成されており、API実装では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メトリクスドキュメントを参照してください。
X-Rayトレーシングの微調整
Section titled “X-Rayトレーシングの微調整”AWS Lambda Powertoolsトレーサーはsrc/middleware/tracer.tsで構成されており、API実装ではopts.ctx.tracerを介してアクセスできます。これを使用して、AWS X-Rayでトレースを追加し、APIリクエストのパフォーマンスとフローに関する詳細な洞察を提供できます。例えば:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { const subSegment = opts.ctx.tracer.getSegment()!.addNewSubsegment('MyAlgorithm'); // ... my algorithm logic to capture subSegment.close();
return ...; });詳細については、AWS Lambda Powertoolsトレーサードキュメントを参照してください。
カスタムミドルウェアの実装
Section titled “カスタムミドルウェアの実装”ミドルウェアを実装することで、プロシージャに提供されるコンテキストに追加の値を追加できます。
例として、src/middleware/identity.tsでAPIの呼び出し元ユーザーに関する詳細を抽出するミドルウェアを実装しましょう。
この例では、IAM認証のIDミドルウェアについて説明します。API Gatewayイベントから抽出されたsubを使用して、Cognitoで呼び出し元を検索します。
まず、コンテキストに追加する内容を定義します:
export interface IIdentityContext { identity?: { sub: string; username: string; };}コンテキストに追加の_オプション_プロパティを定義することに注意してください。tRPCは、このミドルウェアを正しく構成したプロシージャでこれが定義されていることを保証します。
次に、ミドルウェア自体を実装します。これには次の構造があります:
export const createIdentityPlugin = () => { const t = initTRPC.context<...>().create(); return t.procedure.use(async (opts) => { // Add logic here to run before the procedure
const response = await opts.next(...);
// Add logic here to run after the procedure
return response; });};この場合、呼び出し元のCognitoユーザーに関する詳細を抽出したいと思います。API GatewayイベントからユーザーのサブジェクトID(または「sub」)を抽出し、Cognitoからユーザーの詳細を取得します。実装は、イベントがREST APIまたはHTTP APIのどちらから関数に提供されたかによって異なります:
import { CognitoIdentityProvider } from '@aws-sdk/client-cognito-identity-provider';import { initTRPC, TRPCError } from '@trpc/server';import { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';import { APIGatewayProxyEvent } from 'aws-lambda';
export interface IIdentityContext { identity?: { sub: string; username: string; };}
export const createIdentityPlugin = () => { const t = initTRPC.context<IIdentityContext & CreateAWSLambdaContextOptions<APIGatewayProxyEvent>>().create();
const cognito = new CognitoIdentityProvider();
return t.procedure.use(async (opts) => { const cognitoAuthenticationProvider = opts.ctx.event.requestContext?.identity?.cognitoAuthenticationProvider;
let sub: string | undefined = undefined; if (cognitoAuthenticationProvider) { const providerParts = cognitoAuthenticationProvider.split(':'); sub = providerParts[providerParts.length - 1]; }
if (!sub) { throw new TRPCError({ code: 'FORBIDDEN', message: `Unable to determine calling user`, }); }
const { Users } = await cognito.listUsers({ // Assumes user pool id is configured in lambda environment UserPoolId: process.env.USER_POOL_ID!, Limit: 1, Filter: `sub="${sub}"`, });
if (!Users || Users.length !== 1) { throw new TRPCError({ code: 'FORBIDDEN', message: `No user found with subjectId ${sub}`, }); }
// Provide the identity to other procedures in the context return await opts.next({ ctx: { ...opts.ctx, identity: { sub, username: Users[0].Username!, }, }, }); });};import { CognitoIdentityProvider } from '@aws-sdk/client-cognito-identity-provider';import { initTRPC, TRPCError } from '@trpc/server';import { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';import { APIGatewayProxyEventV2WithIAMAuthorizer } from 'aws-lambda';
export interface IIdentityContext { identity?: { sub: string; username: string; };}
export const createIdentityPlugin = () => { const t = initTRPC.context<IIdentityContext & CreateAWSLambdaContextOptions<APIGatewayProxyEventV2WithIAMAuthorizer>>().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: `Unable to determine calling user`, }); }
const { Users } = await cognito.listUsers({ // Assumes user pool id is configured in lambda environment UserPoolId: process.env.USER_POOL_ID!, Limit: 1, Filter: `sub="${sub}"`, });
if (!Users || Users.length !== 1) { throw new TRPCError({ code: 'FORBIDDEN', message: `No user found with subjectId ${sub}`, }); }
// Provide the identity to other procedures in the context return await opts.next({ ctx: { ...opts.ctx, identity: { sub, username: Users[0].Username!, }, }, }); });};auth: 'cognito'でデプロイすると、API Gateway Cognitoオーソライザーは、呼び出し元がAuthorizationヘッダーで提供するJWTを検証し、検証されたクレームをLambdaイベントに配置します。ミドルウェアはこれらのクレームを読み取るだけで、追加のAWS SDK呼び出しや手動のJWT検証は必要ありません。
まず、コンテキストに追加する内容を定義します:
export interface IIdentityContext { identity?: { sub: string; username: string; };}コンテキストに追加の_オプション_プロパティを定義することに注意してください。tRPCは、このミドルウェアを正しく構成したプロシージャでこれが定義されていることを保証します。
次に、ミドルウェア自体:
import { initTRPC, TRPCError } from '@trpc/server';import { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';import { APIGatewayProxyEvent } from 'aws-lambda';
export interface IIdentityContext { identity?: { sub: string; username: string; };}
export const createIdentityPlugin = () => { const t = initTRPC .context<IIdentityContext & CreateAWSLambdaContextOptions<APIGatewayProxyEvent>>() .create();
return t.procedure.use(async (opts) => { const claims = opts.ctx.event.requestContext?.authorizer?.claims as | Record<string, string> | undefined;
const sub = claims?.sub; const username = claims?.username;
if (!sub || !username) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Unable to determine calling user', }); }
return await opts.next({ ctx: { ...opts.ctx, identity: { sub, username, }, }, }); });};その後、呼び出し元のIDが必要なプロシージャにプラグインをミックスできます:
import { publicProcedure } from '../init.js';import { createIdentityPlugin } from '../middleware/identity.js';import { z } from 'zod';
export const me = publicProcedure .concat(createIdentityPlugin()) .output(z.object({ sub: z.string(), username: z.string() })) .query(({ ctx }) => ({ sub: ctx.identity!.sub, username: ctx.identity!.username, }));tRPC APIのデプロイ
Section titled “tRPC APIのデプロイ”tRPC APIジェネレーターは、選択したiacに基づいてCDKまたはTerraformのインフラストラクチャコードを作成します。これを使用してtRPC APIをデプロイできます。
APIをデプロイするためのCDKコンストラクトはcommon/constructsフォルダにあります。これをCDKアプリケーションで使用できます。例えば:
import { MyApi } from '@my-scope/common-constructs';
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { // Add the api to your stack const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), }); }}import { MyApi, UserIdentity } from '@my-scope/common-constructs';
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { // Add the api to your stack const identity = new UserIdentity(this, 'Identity');
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), identity, }); }}UserIdentityコンストラクトは、ts#website#authジェネレーターを使用して生成できます。
これにより、AWS API Gateway RESTまたはHTTP API、ビジネスロジック用のAWS Lambda関数、および選択したauthメソッドに基づく認証を含むAPIインフラストラクチャがセットアップされます。
APIをデプロイするためのTerraformモジュールはcommon/terraformフォルダにあります。これをTerraform構成で使用できます。
APIモジュールは、共有S3アセットバケットにLambdaデプロイメントzipをステージングします。詳細については、Terraformインフラストラクチャガイドを参照してください。デプロイメントごとにcore/asset-bucketモジュールを1回インスタンス化し、そのbucket_name出力をasset_bucket_name入力を介してすべてのAPI / Lambdaモジュールに渡します:
module "asset_bucket" { source = "../../common/terraform/src/core/asset-bucket"}
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# Environment variables for the Lambda function env = { ENVIRONMENT = var.environment LOG_LEVEL = "INFO" }
# Additional IAM policies if needed additional_iam_policy_statements = [ # Add any additional permissions your API needs ]
tags = local.common_tags}module "asset_bucket" { source = "../../common/terraform/src/core/asset-bucket"}
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
user_pool_id = local.user_pool_id user_pool_client_ids = [local.client_id]
# Environment variables for the Lambda function env = { ENVIRONMENT = var.environment LOG_LEVEL = "INFO" }
# Additional IAM policies if needed additional_iam_policy_statements = [ # Add any additional permissions your API needs ]
tags = local.common_tags}適切なTerraformリソースまたはモジュールを使用して、Cognito User PoolとClientをセットアップできます。
これにより、以下がセットアップされます:
- すべてのtRPCプロシージャを提供するAWS Lambda関数
- 関数トリガーとしてのAPI Gateway HTTP/REST API
- IAMロールと権限
- CloudWatchロググループ
- X-Rayトレーシング構成
- CORS構成
Terraformモジュールは、使用できるいくつかの出力を提供します:
# Access the API endpointoutput "api_url" { value = module.my_api.stage_invoke_url}
# Access Lambda function detailsoutput "lambda_function_name" { value = module.my_api.lambda_function_name}
# Access IAM role for granting additional permissionsoutput "lambda_execution_role_arn" { value = module.my_api.lambda_execution_role_arn}モジュールに変数を渡すことで、CORS設定をカスタマイズできます:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# Custom CORS configuration cors_allow_origins = ["https://myapp.com", "https://staging.myapp.com"] cors_allow_methods = ["GET", "POST", "PUT", "DELETE"] cors_allow_headers = [ "authorization", "content-type", "x-custom-header" ]
tags = local.common_tags}REST API の場合、生成されたコンストラクトはデフォルトで AWS WAFv2 Web ACL を API Gateway ステージに関連付けます。Web ACL は AWS マネージド型デフォルトルールセット(AWSManagedRulesCommonRuleSet および AWSManagedRulesKnownBadInputsRuleSet)を使用し、OWASP Top 10 を含む一般的な Web エクスプロイトに対する保護を提供します。WAF リクエストログは CloudWatch Logs グループに書き込まれます。
生成された rest-api コンストラクトを編集して、ルールを追加、削除、または調整できます(例えば、レートベースルールや追加のマネージド型ルールグループを追加するなど)。
オプトアウトするには(例えば、独自の Web ACL をアタッチする場合)、enableWaf を false に設定します:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), enableWaf: false,});オプトアウトするには(例えば、独自の Web ACL をアタッチする場合)、enable_waf を false に設定します:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name enable_waf = false}アクセスログ
Section titled “アクセスログ”REST APIの場合、生成されたインフラストラクチャはデフォルトでアクセスログを有効にし、リクエストごとに1行の構造化JSONを専用のCloudWatch Logsグループに書き込みます。ログループは顧客管理のKMSキーで暗号化され、1年間保持されます。
API Gatewayは、アカウントレベルのCloudWatch Logsロールを使用してアクセスログを書き込みます。このロールはAWS::ApiGateway::Account設定で構成されており、これはリージョンごと、アカウントごとのシングルトンです。つまり、リージョン内のすべてのREST APIに対して1つのロールしか存在しません。独立してデプロイされる複数のスタック間でこれを安全に管理するため、生成されたインフラストラクチャは次のようになっています:
- 共有CloudWatch Logsロールを作成し、動作中のロールがまだ設定されていない場合にのみアカウントに構成するため、デプロイメントが他のスタックが所有するロールを上書きすることはありません。
- ティアダウン時にアカウント設定をそのままにするため、1つのスタックを破棄してもリージョン内の他のREST APIのログ記録が無効になることはありません。
アカウントロールはApiGatewayAccountコンストラクトによって管理されます。これはApiGatewayAccount.ensure(scope)を介して解決されるスタックスコープのシングルトンです。各REST APIのステージはこれに依存しており、ロールはLambdaバックのカスタムリソースによって構成されます。
アクセスログフォーマットは、APIが拡張するRestApiコンストラクトによって設定されます。カスタマイズするには、生成されたpackages/common/constructs/src/app/apis/my-api.ts内でdeployOptionsをsuperに渡し、コンストラクトがすでに設定しているtracingEnabledを保持します:
super(scope, id, { apiName: 'MyApi', // ... deployOptions: { tracingEnabled: true, accessLogFormat: AccessLogFormat.clf(), }, ...props,});AccessLogFormatはaws-cdk-lib/aws-apigatewayからインポートされます。設定しないものはすべて、コンストラクトのデフォルト(標準フィールドを持つJSON形式)を保持します。
アカウントロールはcore/api/api-gateway-accountモジュールによって管理されます。これは生成されたAPIモジュールによってインスタンス化されます。アカウントをべき等に構成し、terraform destroy時にリセットされることはありません。
生成されたAPIモジュール内のaws_api_gateway_stageリソースのaccess_log_settingsブロックを編集することで、アクセスログフォーマットをカスタマイズできます。
REST/HTTP API CDK コンストラクトは、各オペレーションのインテグレーションを定義するための型安全なインターフェースを提供するように構成されています。
デフォルトインテグレーション
Section titled “デフォルトインテグレーション”静的な defaultIntegrations を使用して、各オペレーションに個別の AWS Lambda 関数を定義するデフォルトパターンを利用できます:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});Terraform モジュールは、単一の Lambda 関数を使用するルーターパターンを自動的に使用します。追加の設定は必要ありません:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# The module automatically creates a single Lambda function # that handles all API operations tags = local.common_tags}インテグレーションへのアクセス
Section titled “インテグレーションへのアクセス”API コンストラクトの integrations プロパティを介して、基礎となる AWS Lambda 関数に型安全な方法でアクセスできます。例えば、API が sayHello という名前のオペレーションを定義していて、この関数にいくつかの権限を追加する必要がある場合、次のように実行できます:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
// sayHello is typed to the operations defined in your APIapi.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ effect: Effect.ALLOW, actions: [...], resources: [...],}));API が shared パターンを使用している場合、共有ルーター Lambda は api.integrations.$router として公開されます:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
api.integrations.$router.handler.addEnvironment('LOG_LEVEL', 'DEBUG');Terraform のルーターパターンでは、Lambda 関数は 1 つだけです。モジュールの出力を介してアクセスできます:
# Grant additional permissions to the single Lambda functionresource "aws_iam_role_policy" "additional_permissions" { name = "additional-api-permissions" role = module.my_api.lambda_execution_role_name
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "s3:GetObject", "s3:PutObject" ] Resource = "arn:aws:s3:::my-bucket/*" } ] })}デフォルトオプションのカスタマイズ
Section titled “デフォルトオプションのカスタマイズ”各デフォルトインテグレーションの Lambda 関数を作成する際に使用されるオプションをカスタマイズしたい場合は、withDefaultOptions メソッドを使用できます。例えば、すべての Lambda 関数を Vpc 内に配置したい場合:
const vpc = new Vpc(this, 'Vpc', ...);
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withDefaultOptions({ vpc, }) .build(),});VPC 設定は生成されたモジュールによって既にサポートされています — enable_vpc を vpc_id および subnet_ids と共に設定すると、モジュールは Lambda 関数を VPC 内に、作成されたセキュリティグループの背後にデプロイします:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# VPC configuration enable_vpc = true vpc_id = aws_vpc.main.id subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id]
tags = local.common_tags}モジュールが公開していないオプションについては、生成された Terraform モジュール内の aws_lambda_function リソースを直接編集してください。
オペレーションごとのオプションのカスタマイズ
Section titled “オペレーションごとのオプションのカスタマイズ”_特定の_オペレーションのデフォルトインテグレーションを作成するために使用されるオプションをカスタマイズする(他のオペレーションに影響を与えずに)には、withOperationOptions メソッドを使用できます。例えば、1 つのオペレーションだけの Lambda 関数タイムアウトを増やしたい場合:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOperationOptions({ sayHello: { timeout: Duration.seconds(60), }, }) .build(),});
// The selected operations remain default integrations, so they're still typed accordingly:api.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ ... }));指定したオプションは、デフォルトインテグレーションオプション(および withDefaultOptions で設定されたオプション)とマージされます。withOverrides で置き換えたオペレーションにはオプションを指定できないことに注意してください。これらはデフォルトインテグレーションを使用しなくなるためです。
withOperationOptions と withOverrides の両方で同じオペレーションをターゲットにすると、呼び出す順序に関係なく型エラーが発生します。
Terraform で特定のオペレーションのオプションをカスタマイズするには、生成された Terraform モジュールを編集して、オペレーションごとに個別の Lambda 関数を設定する必要があります(以下の明示的なインテグレーションセクションを参照してください)。
インテグレーションのオーバーライド
Section titled “インテグレーションのオーバーライド”withOverrides メソッドを使用して、特定のオペレーションのインテグレーションをオーバーライドすることもできます。各オーバーライドは、HTTP または REST API の適切な CDK インテグレーションコンストラクトに型付けされた integration プロパティを指定する必要があります。withOverrides メソッドも型安全です。例えば、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(),});
// Later, perhaps in another file, you can access the bucket property we defined// in a type-safe mannerapi.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(...) // for REST, or HttpUserPoolAuthorizer for an HTTP API } }, }) .build(),});明示的なインテグレーション
Section titled “明示的なインテグレーション”必要に応じて、デフォルトインテグレーションを使用せず、各オペレーションに直接インテグレーションを提供することもできます。これは、例えば各オペレーションが異なるタイプのインテグレーションを使用する必要がある場合や、新しいオペレーションを追加する際に型エラーを受け取りたい場合に便利です:
new MyApi(this, 'MyApi', { integrations: { sayHello: { integration: new LambdaIntegration(...), }, getDocumentation: { integration: new HttpIntegration(...), }, },});Terraform で明示的なオペレーションごとのインテグレーションを使用する場合は、生成されたアプリ固有のモジュールを変更して、デフォルトのプロキシインテグレーションを各オペレーションの特定のインテグレーションに置き換える必要があります。
packages/common/terraform/src/app/apis/my-api/my-api.tf を編集します:
- デフォルトのプロキシルートを削除(例:
resource "aws_apigatewayv2_route" "proxy_routes") - 単一の Lambda 関数を置き換え、各オペレーションに個別の関数を作成
- 各オペレーションの特定のインテグレーションとルートを作成、同じ ZIP バンドルを再利用:
# Remove the default single Lambda function resource "aws_lambda_function" "api_lambda" { s3_bucket = aws_s3_object.lambda_zip.bucket s3_key = aws_s3_object.lambda_zip.key s3_object_version = aws_s3_object.lambda_zip.version_id function_name = "MyApiHandler" role = aws_iam_role.lambda_execution_role.arn handler = "index.handler" runtime = "nodejs22.x" timeout = 30 # ... rest of configuration }
# Remove the default proxy integration resource "aws_apigatewayv2_integration" "lambda_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.api_lambda.invoke_arn # ... rest of configuration }
# Remove the default proxy routes resource "aws_apigatewayv2_route" "proxy_routes" { for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]) api_id = module.http_api.api_id route_key = "${each.key} /{proxy+}" target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}" # ... rest of configuration }
# Add individual Lambda functions for each operation using the same bundle resource "aws_lambda_function" "say_hello_handler" { s3_bucket = aws_s3_object.lambda_zip.bucket s3_key = aws_s3_object.lambda_zip.key s3_object_version = aws_s3_object.lambda_zip.version_id function_name = "MyApi-SayHello" role = aws_iam_role.lambda_execution_role.arn handler = "sayHello.handler" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
resource "aws_lambda_function" "get_documentation_handler" { s3_bucket = aws_s3_object.lambda_zip.bucket s3_key = aws_s3_object.lambda_zip.key s3_object_version = aws_s3_object.lambda_zip.version_id function_name = "MyApi-GetDocumentation" role = aws_iam_role.lambda_execution_role.arn handler = "getDocumentation.handler" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
# Add specific integrations for each operation resource "aws_apigatewayv2_integration" "say_hello_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.say_hello_handler.invoke_arn payload_format_version = "2.0" timeout_milliseconds = 30000 }
resource "aws_apigatewayv2_integration" "get_documentation_integration" { api_id = module.http_api.api_id integration_type = "HTTP_PROXY" integration_uri = "https://example.com/documentation" integration_method = "GET" }
# Add specific routes for each operation resource "aws_apigatewayv2_route" "say_hello_route" { api_id = module.http_api.api_id route_key = "POST /sayHello" target = "integrations/${aws_apigatewayv2_integration.say_hello_integration.id}" authorization_type = "AWS_IAM" }
resource "aws_apigatewayv2_route" "get_documentation_route" { api_id = module.http_api.api_id route_key = "GET /documentation" target = "integrations/${aws_apigatewayv2_integration.get_documentation_integration.id}" authorization_type = "NONE" }
# Add Lambda permissions for each function resource "aws_lambda_permission" "say_hello_permission" { statement_id = "AllowExecutionFromAPIGateway-SayHello" action = "lambda:InvokeFunction" function_name = aws_lambda_function.say_hello_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.http_api.api_execution_arn}/*/*" }
resource "aws_lambda_permission" "get_documentation_permission" { statement_id = "AllowExecutionFromAPIGateway-GetDocumentation" action = "lambda:InvokeFunction" function_name = aws_lambda_function.get_documentation_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.http_api.api_execution_arn}/*/*" }# Remove the default single Lambda function resource "aws_lambda_function" "api_lambda" { s3_bucket = aws_s3_object.lambda_zip.bucket s3_key = aws_s3_object.lambda_zip.key s3_object_version = aws_s3_object.lambda_zip.version_id function_name = "MyApiHandler-${random_string.suffix.result}" role = aws_iam_role.lambda_execution_role.arn handler = "index.handler" runtime = "nodejs22.x" timeout = 30 # ... rest of configuration }
# Remove the default proxy integration resource "aws_api_gateway_integration" "lambda_integration" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.proxy_resource.id http_method = aws_api_gateway_method.proxy_method.http_method integration_http_method = "POST" type = "AWS_PROXY" uri = aws_lambda_function.api_lambda.invoke_arn # ... rest of configuration }
# Remove the default catch-all proxy method resource "aws_api_gateway_method" "proxy_method" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.proxy_resource.id http_method = "ANY" # ... rest of configuration }
# Add individual Lambda functions for each operation using the same bundle resource "aws_lambda_function" "say_hello_handler" { s3_bucket = aws_s3_object.lambda_zip.bucket s3_key = aws_s3_object.lambda_zip.key s3_object_version = aws_s3_object.lambda_zip.version_id function_name = "MyApi-SayHello" role = aws_iam_role.lambda_execution_role.arn handler = "sayHello.handler" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
resource "aws_lambda_function" "get_documentation_handler" { s3_bucket = aws_s3_object.lambda_zip.bucket s3_key = aws_s3_object.lambda_zip.key s3_object_version = aws_s3_object.lambda_zip.version_id function_name = "MyApi-GetDocumentation" role = aws_iam_role.lambda_execution_role.arn handler = "getDocumentation.handler" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
# Add specific resources and methods for each operation resource "aws_api_gateway_resource" "say_hello_resource" { rest_api_id = module.rest_api.api_id parent_id = module.rest_api.api_root_resource_id path_part = "sayHello" }
resource "aws_api_gateway_method" "say_hello_method" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.say_hello_resource.id http_method = "POST" authorization = "AWS_IAM" }
resource "aws_api_gateway_integration" "say_hello_integration" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.say_hello_resource.id http_method = aws_api_gateway_method.say_hello_method.http_method
integration_http_method = "POST" type = "AWS_PROXY" uri = aws_lambda_function.say_hello_handler.invoke_arn }
resource "aws_api_gateway_resource" "get_documentation_resource" { rest_api_id = module.rest_api.api_id parent_id = module.rest_api.api_root_resource_id path_part = "documentation" }
resource "aws_api_gateway_method" "get_documentation_method" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.get_documentation_resource.id http_method = "GET" authorization = "NONE" }
resource "aws_api_gateway_integration" "get_documentation_integration" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.get_documentation_resource.id http_method = aws_api_gateway_method.get_documentation_method.http_method
integration_http_method = "GET" type = "HTTP" uri = "https://example.com/documentation" }
# Update deployment to depend on new integrations~ resource "aws_api_gateway_deployment" "api_deployment" { rest_api_id = module.rest_api.api_id
depends_on = [ aws_api_gateway_integration.lambda_integration, aws_api_gateway_integration.say_hello_integration, aws_api_gateway_integration.get_documentation_integration, ]
lifecycle { create_before_destroy = true }
triggers = { redeployment = sha1(jsonencode([ aws_api_gateway_integration.say_hello_integration, aws_api_gateway_integration.get_documentation_integration, ])) } }
# Add Lambda permissions for each function resource "aws_lambda_permission" "say_hello_permission" { statement_id = "AllowExecutionFromAPIGateway-SayHello" action = "lambda:InvokeFunction" function_name = aws_lambda_function.say_hello_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.rest_api.api_execution_arn}/*/*" }
resource "aws_lambda_permission" "get_documentation_permission" { statement_id = "AllowExecutionFromAPIGateway-GetDocumentation" action = "lambda:InvokeFunction" function_name = aws_lambda_function.get_documentation_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.rest_api.api_execution_arn}/*/*" }インテグレーションパターン
Section titled “インテグレーションパターン”生成された CDK API コンストラクトは、2 つのインテグレーションパターンをサポートしています:
isolatedは、オペレーションごとに 1 つの Lambda 関数を作成します。これは生成された API のデフォルトです。sharedは、単一のデフォルトルーター Lambda を作成し、特定のインテグレーションをオーバーライドしない限り、すべてのオペレーションでそれを再利用します。
isolated は、オペレーションごとにより細かい権限と設定を提供します。shared は、選択的なオーバーライドを許可しながら、Lambda と API Gateway インテグレーションの拡散を削減します。
例えば、pattern を 'shared' に設定すると、オペレーションごとに 1 つではなく、単一の関数が作成されます:
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => { ... return IntegrationBuilder.rest({ pattern: 'shared', ... }); };}Terraform モジュールは自動的にルーターパターンを使用します - これはデフォルトであり、唯一サポートされているアプローチです。生成されたモジュールは、すべての API オペレーションを処理する単一の Lambda 関数を作成します。
デフォルトのモジュールをインスタンス化するだけで、ルーターパターンを取得できます:
# Default router pattern - single Lambda function for all operationsmodule "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# Single Lambda function handles all operations automatically tags = local.common_tags}アクセスの付与(IAMのみ)
Section titled “アクセスの付与(IAMのみ)”次のようにAPIへのアクセスを付与できます:
api.grantInvokeAccess(myIdentityPool.authenticatedRole);# Create an IAM policy to allow invoking the APIresource "aws_iam_policy" "api_invoke_policy" { name = "MyApiInvokePolicy" description = "Policy to allow invoking the tRPC API"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "execute-api:Invoke" Resource = "${module.my_api.api_execution_arn}/*/*" } ] })}
# Attach the policy to an IAM role (e.g., for authenticated users)resource "aws_iam_role_policy_attachment" "api_invoke_access" { role = aws_iam_role.authenticated_user_role.name policy_arn = aws_iam_policy.api_invoke_policy.arn}
# Or attach to an existing role by nameresource "aws_iam_role_policy_attachment" "api_invoke_access_existing" { role = "MyExistingRole" policy_arn = aws_iam_policy.api_invoke_policy.arn}IAMポリシーに使用できるAPIモジュールの主要な出力は次のとおりです:
module.my_api.api_execution_arn- execute-api:Invoke権限を付与するためmodule.my_api.api_arn- API Gateway ARNmodule.my_api.lambda_function_arn- Lambda関数ARN
バンドルターゲット
Section titled “バンドルターゲット”ジェネレーターは、Rolldown を使用してデプロイメントパッケージを作成する bundle ターゲットを自動的に設定します:
pnpm nx bundle <project-name>yarn nx bundle <project-name>npx nx bundle <project-name>bunx nx bundle <project-name>Rolldown の設定は rolldown.config.ts にあり、生成するバンドルごとにエントリーがあります。Rolldown は、定義されている場合、複数のバンドルを並列で作成することを管理します。
ローカルtRPCサーバー
Section titled “ローカルtRPCサーバー”serveターゲットを使用して、APIのローカルサーバーを実行できます。例えば:
pnpm nx serve my-apiyarn nx serve my-apinpx nx serve my-apibunx nx serve my-apiローカルサーバーのエントリーポイントはsrc/local-server.tsです。
これにより、APIに変更を加えると自動的にリロードされます。
tRPC APIの呼び出し
Section titled “tRPC APIの呼び出し”tRPCクライアントを作成して、型安全な方法でAPIを呼び出すことができます。別のバックエンドからtRPC APIを呼び出す場合は、src/client/index.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を呼び出す場合は、Connectionジェネレーターを使用してクライアントを構成することを検討してください。
tRPCの詳細については、tRPCドキュメントを参照してください。
connectionジェネレーターを使用して、このプロジェクトをワークスペース内の他のプロジェクトと統合します。このプロジェクトに関連する接続は次のとおりです: