Smithy TypeScript API
Smithy は、モデル駆動型の方法で API を作成するためのプロトコルに依存しないインターフェース定義言語です。
Smithy TypeScript API ジェネレーターは、サービス定義に Smithy を使用し、実装に Smithy TypeScript Server SDK を使用して新しい API を作成します。このジェネレーターは、AWS Lambda にサービスをデプロイし、AWS API Gateway REST API 経由で公開するための CDK または Terraform のインフラストラクチャコードを提供します。Smithy モデルからの自動コード生成により、型安全な API 開発を実現します。生成されたハンドラーは、ログ記録、AWS X-Ray トレース、CloudWatch メトリクスなどの可観測性のために AWS Lambda Powertools for TypeScript を使用します。
Smithy TypeScript API の生成
Section titled “Smithy TypeScript API の生成”新しい Smithy TypeScript API は 2 つの方法で生成できます:
pnpm nx g @aws/nx-plugin:ts#api --framework=smithyyarn nx g @aws/nx-plugin:ts#api --framework=smithynpx nx g @aws/nx-plugin:ts#api --framework=smithybunx nx g @aws/nx-plugin:ts#api --framework=smithy変更されるファイルを確認するためにドライランを実行することもできます
pnpm nx g @aws/nx-plugin:ts#api --framework=smithy --dry-runyarn nx g @aws/nx-plugin:ts#api --framework=smithy --dry-runnpx nx g @aws/nx-plugin:ts#api --framework=smithy --dry-runbunx nx g @aws/nx-plugin:ts#api --framework=smithy --dry-run- インストール Nx Console VSCode Plugin まだインストールしていない場合
- VSCodeでNxコンソールを開く
- クリック
Generate (UI)"Common Nx Commands"セクションで - 検索
@aws/nx-plugin - ts#api - 必須パラメータを入力
- framework: smithy
- クリック
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> ディレクトリに 2 つの関連プロジェクトを作成します:
Directorymodel/ Smithy モデルプロジェクト
- package.json プロジェクトのパッケージ名と依存関係を定義するプロジェクトマニフェスト
- project.json プロジェクト設定とビルドターゲット
- smithy-build.json Smithy ビルド設定
- ssdk.rolldown.config.mjs 生成された TypeScript Server SDK をバンドル
Directorysrc/
- main.smithy メインサービス定義
Directoryoperations/
- echo.smithy サンプルオペレーション定義
Directorybackend/ TypeScript バックエンド実装
- project.json プロジェクト設定とビルドターゲット
- rolldown.config.ts バンドル設定
Directorysrc/
- handler.ts AWS Lambda ハンドラー
- local-server.ts ローカル開発サーバー
- service.ts サービス実装
- context.ts サービスコンテキスト定義
Directoryoperations/
- echo.ts サンプルオペレーション実装
Directorygenerated/ 生成された TypeScript SDK(ビルド時に作成)
- …
インフラストラクチャ
Section titled “インフラストラクチャ”このジェネレーターは選択した iac に基づいてインフラストラクチャコードを作成するため、packages/common に関連する CDK コンストラクトまたは Terraform モジュールを含むプロジェクトが作成されます。
共通のインフラストラクチャコードプロジェクトは次のように構成されています:
Directorypackages/common/constructs
Directorysrc
Directoryapp/ プロジェクト/ジェネレーター固有のインフラストラクチャ用コンストラクト
Directoryapis/
- <project-name>.ts API をデプロイするための CDK コンストラクト
Directorycore/
appのコンストラクトで再利用される汎用コンストラクトDirectoryapi/
- rest-api.ts REST API をデプロイするための CDK コンストラクト
- utils.ts API コンストラクト用ユーティリティ
- index.ts
appからコンストラクトをエクスポートするエントリーポイント
- project.json プロジェクトビルドターゲットと設定
Directorypackages/common/terraform
Directorysrc
Directoryapp/ プロジェクト/ジェネレーター固有のインフラストラクチャ用 Terraform モジュール
Directoryapis/
Directory<project-name>/
- <project-name>.tf API をデプロイするためのモジュール
Directorycore/
appのモジュールで再利用される汎用モジュールDirectoryapi/
Directoryrest-api/
- rest-api.tf REST API をデプロイするためのモジュール
- project.json プロジェクトビルドターゲットと設定
アーキテクチャ
Section titled “アーキテクチャ”デプロイされた Smithy API は次のアーキテクチャを持ち、API Gateway ステージの前に AWS WAFv2 Web ACL が配置されます:
Smithy API の実装
Section titled “Smithy API の実装”Smithy でのオペレーションの定義
Section titled “Smithy でのオペレーションの定義”オペレーションは、モデルプロジェクト内の Smithy ファイルで定義されます。メインサービス定義は main.smithy にあります:
$version: "2.0"
namespace your.namespace
use aws.protocols#restJson1use smithy.framework#ValidationException
@title("YourService")@restJson1service YourService { version: "1.0.0" operations: [ Echo, // Add your operations here ] errors: [ ValidationException ]}個々のオペレーションは operations/ ディレクトリ内の別々のファイルで定義されます:
$version: "2.0"
namespace your.namespace
@http(method: "POST", uri: "/echo")operation Echo { input: EchoInput output: EchoOutput}
structure EchoInput { @required message: String
foo: Integer bar: String}
structure EchoOutput { @required message: String}シェイプライブラリの追加
Section titled “シェイプライブラリの追加”同じデータ型を共有する複数の Smithy API がある場合、それらの型を各モデルで重複させるのではなく、シェイプライブラリで一度定義できます。シェイプライブラリは、サービスを持たない Smithy プロジェクトで、再利用可能なシェイプのみを持ち、任意の数の Smithy プロジェクトが依存できます。
smithy#project ジェネレーターで生成します:
pnpm nx g @aws/nx-plugin:smithy#project --name=my-shapes --type=shapesyarn nx g @aws/nx-plugin:smithy#project --name=my-shapes --type=shapesnpx nx g @aws/nx-plugin:smithy#project --name=my-shapes --type=shapesbunx nx g @aws/nx-plugin:smithy#project --name=my-shapes --type=shapes変更されるファイルを確認するためにドライランを実行することもできます
pnpm nx g @aws/nx-plugin:smithy#project --name=my-shapes --type=shapes --dry-runyarn nx g @aws/nx-plugin:smithy#project --name=my-shapes --type=shapes --dry-runnpx nx g @aws/nx-plugin:smithy#project --name=my-shapes --type=shapes --dry-runbunx nx g @aws/nx-plugin:smithy#project --name=my-shapes --type=shapes --dry-run- インストール Nx Console VSCode Plugin まだインストールしていない場合
- VSCodeでNxコンソールを開く
- クリック
Generate (UI)"Common Nx Commands"セクションで - 検索
@aws/nx-plugin - smithy#project - 必須パラメータを入力
- name: my-shapes
- type: shapes
- クリック
Generate
API のモデルは、use を使用してそのシェイプを参照できます:
$version: "2.0"
namespace com.example.api
use com.example.shared#Customer
structure GetCustomerOutput { @required customer: Customer}シェイプライブラリを作成し、API のモデルの依存関係として接続する方法については、Smithy プロジェクトガイドを参照してください。
TypeScript でのオペレーションの実装
Section titled “TypeScript でのオペレーションの実装”オペレーション実装は、バックエンドプロジェクトの src/operations/ ディレクトリにあります。各オペレーションは、TypeScript Server SDK から生成された型を使用して実装されます(Smithy モデルからビルド時に生成されます)。
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input) => { // Your business logic here return { message: `Echo: ${input.message}` // type-safe based on your Smithy model };};オペレーションは src/service.ts のサービス定義に登録する必要があります:
import { ServiceContext } from './context.js';import { YourServiceService } from './generated/ssdk/index.js';import { Echo } from './operations/echo.js';// Import other operations here
// Register operations to the service hereexport const Service: YourServiceService<ServiceContext> = { Echo, // Add other operations here};サービスコンテキスト
Section titled “サービスコンテキスト”context.ts でオペレーション用の共有コンテキストを定義できます:
export interface ServiceContext { // Powertools tracer, logger and metrics are provided by default tracer: Tracer; logger: Logger; metrics: Metrics; // Add shared dependencies, database connections, etc. dbClient: any; userIdentity: string;}このコンテキストはすべてのオペレーション実装に渡され、データベース接続、設定、ログユーティリティなどのリソースを共有するために使用できます。
AWS Lambda Powertools による可観測性
Section titled “AWS Lambda Powertools による可観測性”ジェネレーターは、Middy ミドルウェアによる自動コンテキスト注入を使用して、AWS Lambda Powertools で構造化ログを設定します。
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);コンテキスト経由でオペレーション実装からロガーを参照できます:
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { ctx.logger.info('Your log message'); // ...};AWS X-Ray トレースは captureLambdaHandler ミドルウェアによって自動的に設定されます。
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);オペレーション内でトレースにカスタムサブセグメントを追加できます:
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { // Creates a new subsegment const subsegment = ctx.tracer.getSegment()?.addNewSubsegment('custom-operation'); try { // Your logic here } catch (error) { subsegment?.addError(error as Error); throw error; } finally { subsegment?.close(); }};CloudWatch メトリクスは、logMetrics ミドルウェアによって各リクエストに対して自動的に収集されます。
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);オペレーション内でカスタムメトリクスを追加できます:
import { MetricUnit } from '@aws-lambda-powertools/metrics';import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { ctx.metrics.addMetric("CustomMetric", MetricUnit.Count, 1); // ...};Smithy は組み込みのエラー処理を提供します。Smithy モデルでカスタムエラーを定義できます:
@error("client")@httpError(400)structure InvalidRequestError { @required message: String}そして、オペレーション/サービスに登録します:
operation MyOperation { ... errors: [InvalidRequestError]}次に、TypeScript 実装でそれらをスローします:
import { InvalidRequestError } from '../generated/ssdk/index.js';
export const MyOperation: MyOperationHandler<ServiceContext> = async (input) => { if (!input.requiredField) { throw new InvalidRequestError({ message: "Required field is missing" }); }
return { /* success response */ };};呼び出し元ユーザーへのアクセス
Section titled “呼び出し元ユーザーへのアクセス”API が認証によって保護されている場合、オペレーションは誰が呼び出しているかを知る必要があることがよくあります。推奨されるアプローチは、ハンドラーで呼び出し元の ID を一度解決し、特定のオペレーションで使用するためにサービスコンテキストを通じて渡すことです。
未承認のケースを Smithy エラーとしてモデル化し、適切な 403 レスポンスにシリアライズされるようにします。モデルに追加します。例えば model/src/operations/errors.smithy に追加し、ID を必要とする任意のオペレーションで参照します:
$version: "2.0"
namespace your.namespace
/// Thrown when the calling user cannot be determined@error("client")@httpError(403)structure UnauthorizedError { @required message: String}まず、src/context.ts のサービスコンテキストで解決された ID を公開します。UnauthorizedError がオペレーション内からスローされるように(Server SDK がそれを 403 にシリアライズする場所)、ハンドラーからではなく、関数として提供します:
import { Logger } from '@aws-lambda-powertools/logger';import { Metrics } from '@aws-lambda-powertools/metrics';import { Tracer } from '@aws-lambda-powertools/tracer';
export interface Identity { sub: string; username: string;}
/** * Context provided to all operations. */export interface ServiceContext { tracer: Tracer; logger: Logger; metrics: Metrics; getIdentity: () => Promise<Identity>;}次に、src/identity.ts にリゾルバーを記述します。呼び出し元を判別できない場合は UnauthorizedError をスローします。実装は選択した auth メソッドによって異なります:
IAM 認証の場合、API Gateway イベントから抽出された sub を使用して Cognito で呼び出し元を検索します:
import { CognitoIdentityProvider } from '@aws-sdk/client-cognito-identity-provider';import type { APIGatewayProxyEvent } from 'aws-lambda';import { Identity } from './context.js';import { UnauthorizedError } from './generated/ssdk/index.js';
const cognito = new CognitoIdentityProvider();
export const getIdentity = async ( event: APIGatewayProxyEvent,): Promise<Identity> => { const cognitoAuthenticationProvider = event.requestContext?.identity?.cognitoAuthenticationProvider;
let sub: string | undefined = undefined; if (cognitoAuthenticationProvider) { const providerParts = cognitoAuthenticationProvider.split(':'); sub = providerParts[providerParts.length - 1]; }
if (!sub) { throw new UnauthorizedError({ 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 UnauthorizedError({ message: `No user found with subjectId ${sub}` }); }
return { sub, username: Users[0].Username! };};auth: 'cognito' の場合、API Gateway Cognito User Pools オーソライザーは、呼び出し元が Authorization ヘッダーで提供する JWT を検証し、検証されたクレームを event.requestContext.authorizer.claims のイベントに配置します:
import type { APIGatewayProxyEvent } from 'aws-lambda';import { Identity } from './context.js';import { UnauthorizedError } from './generated/ssdk/index.js';
export const getIdentity = async ( event: APIGatewayProxyEvent,): Promise<Identity> => { const claims = event.requestContext?.authorizer?.claims as | Record<string, string> | undefined;
const sub = claims?.sub; const username = claims?.username;
if (!sub || !username) { throw new UnauthorizedError({ message: 'Unable to determine calling user' }); }
return { sub, username };};次に、src/handler.ts でリゾルバーをコンテキストに接続します:
import { Service } from './service.js';import { getIdentity } from './identity.js';// ...const httpResponse = await serviceHandler.handle(httpRequest, { tracer, logger, metrics, getIdentity: () => getIdentity(event),});これで、オペレーション内で解決された ID を使用できます。例えば src/operations/echo.ts で:
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { const identity = await ctx.getIdentity(); return { message: `${identity.username} says ${input.message}` };};ビルドとコード生成
Section titled “ビルドとコード生成”Smithy モデルプロジェクトは、Smithy CLI を使用して Smithy アーティファクトをビルドし、TypeScript Server SDK を生成します:
pnpm nx build <model-project>yarn nx build <model-project>npx nx build <model-project>bunx nx build <model-project>macOS と Linux では、CLI は mise によって解決され、ビルドがオンデマンドで取得するため、インストールするものはありません — 初回ビルド時にピン留めされたバージョンをダウンロードしてキャッシュします。
このプロセスは:
- Smithy モデルをコンパイルして検証します
- Smithy モデルから OpenAPI 仕様を生成します
- 型安全なオペレーションインターフェースを持つ TypeScript Server SDK を作成します
- ビルドアーティファクトを出力します(
dist/<model-project>/build/に)
バックエンドプロジェクトは、コンパイル中に生成された SDK を自動的にコピーします:
pnpm nx copy-ssdk <backend-project>yarn nx copy-ssdk <backend-project>npx nx copy-ssdk <backend-project>bunx nx copy-ssdk <backend-project>Windows でのビルド
Section titled “Windows でのビルド”mise は npm に Windows パッケージを公開していないため、Windows では Smithy CLI は自分でインストールする前提条件です。Smithy CLI インストールガイドに従って一度インストールし(例:winget install smithy または scoop install smithy)、smithy が PATH にあることを確認してください。Windows で生成された Smithy プロジェクトは、mise を介してではなく smithy を直接実行します。
または、WSL 内で開発すると、ビルドは Linux パスを実行し、mise が CLI を解決します — インストールするものはありません。
Windows で生成されたプロジェクトは、smithy を直接呼び出す compile ターゲットをコミットするため、それに取り組む他の誰か(macOS や Linux を含む)も PATH に Smithy CLI が必要です。これらのマシンで代わりに mise を介して CLI を解決するには、以下で説明するように、ターゲットを mise コマンドに切り替えます。
CLI の解決方法の選択
Section titled “CLI の解決方法の選択”macOS と Linux は mise を介して CLI を解決し、Windows はグローバルにインストールされた CLI を使用しますが、モデルプロジェクトの project.json の compile ターゲットのコマンドを編集することで、任意のプラットフォームでどちらかを選択できます。
mise の代わりにグローバルにインストールされた Smithy CLI を使用するには、mise プレフィックスを単なる smithy に置き換えます:
{ "targets": { "compile": { "options": { "commands": ["... npx -y mise@<version> exec smithy@<version> -- smithy build ..."] "commands": ["... smithy build ..."] } } }}mise で CLI を解決するように戻すには、npx -y mise@<version> exec smithy@<version> -- プレフィックスを復元します。
バンドルターゲット
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 は、定義されている場合、複数のバンドルを並列で作成することを管理します。
ローカル開発
Section titled “ローカル開発”ジェネレーターは、ホットリロード機能を備えたローカル開発サーバーを設定します:
pnpm nx serve <backend-project>yarn nx serve <backend-project>npx nx serve <backend-project>bunx nx serve <backend-project>Smithy API のデプロイ
Section titled “Smithy API のデプロイ”ジェネレーターは、選択した iac に基づいて CDK または Terraform インフラストラクチャを作成します。
API をデプロイするための CDK コンストラクトは common/constructs フォルダーにあります:
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(), }); }}これにより次が設定されます:
- Smithy サービス用の AWS Lambda 関数
- 関数トリガーとしての API Gateway REST API
- IAM ロールと権限
- CloudWatch ロググループ
- X-Ray トレース設定
API をデプロイするための Terraform モジュールは common/terraform フォルダーにあります。
API モジュールは、Lambda デプロイ zip を共有 S3 アセットバケットにステージングします — 詳細については Terraform インフラストラクチャガイドを参照してください。デプロイごとに core/asset-bucket モジュールを一度インスタンス化し、その 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}これにより次が設定されます:
- Smithy API を提供する AWS Lambda 関数
- 関数トリガーとしての API Gateway 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}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}オペレーションは Smithy で定義されているため、コード生成を使用して、型安全な統合のためのメタデータを CDK コンストラクトに提供します。
generate:<ApiName>-metadata ターゲットが共通コンストラクトの project.json に追加され、このコード生成を容易にします。これは packages/common/constructs/src/generated/my-api/metadata.gen.ts のようなファイルを出力します。これはビルド時に生成されるため、バージョン管理では無視されます。
アクセス権限付与(IAMのみ)
Section titled “アクセス権限付与(IAMのみ)”IAM認証を選択した場合、grantInvokeAccessメソッドで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 Smithy 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 roleresource "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}Smithy API の呼び出し
Section titled “Smithy API の呼び出し”React ウェブサイトから API を呼び出すには、connection ジェネレーターを使用できます。これは Smithy モデルから型安全なクライアント生成を提供します。
connection ジェネレーターを使用して、このプロジェクトをワークスペース内の他のプロジェクトと統合します。このプロジェクトに関連する次の接続があります: