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 つの方法で生成できます:
このジェネレーターを実行@aws/nx-plugin:ts#api
pnpm nx g @aws/nx-plugin:ts#api yarn nx g @aws/nx-plugin:ts#api npx nx g @aws/nx-plugin:ts#api bunx nx g @aws/nx-plugin:ts#api- インストール Nx Console VSCode Plugin まだインストールしていない場合
- VSCodeでNxコンソールを開く
- クリック
Generate (UI)"Common Nx Commands"セクションで - 検索
@aws/nx-plugin - ts#api - 必須パラメータを入力
- クリック
Generate
コマンドを組み立てる10
必須
framework = smithy
name必須stringAPIの名前(必須)。クラス名とファイルパスの生成に使用されます。
frameworkenumデフォルト:trpc使用するAPIフレームワーク。
trpcsmithyintegrationPatternenumデフォルト:isolatedAPI用にAPI Gateway統合を生成する方法。isolated(デフォルト)またはsharedから選択します。
isolatedsharedauthenumデフォルト:iamAPIの認証に使用する方法。iam(デフォルト)、cognito、customから選択します。
iamcognitocustomdirectorystringデフォルト:packagesアプリケーションを保存するディレクトリ。
iacenumデフォルト:inherit優先するIaCプロバイダー。デフォルトでは初期選択から継承されます。
inheritcdkterraforminfraenumデフォルト:rest-lambdaこのAPIをデプロイするために使用するインフラストラクチャのタイプ。
rest-lambdanonenamespacestringframework = smithySmithy APIの名前空間(smithyフレームワークにのみ適用されます)。デフォルトはモノレポのスコープです
subDirectorystringプロジェクトが配置されるサブディレクトリ。デフォルトではプロジェクト名になります。
preferInstallDependenciesbooleanデフォルト:trueジェネレーター実行後に依存関係のインストールを優先するかどうか。複数のジェネレーターをバッチ処理する際にインストールを延期する場合は false に設定します(後続のジェネレーターが Nx プロジェクトグラフを計算できるよう、必要に応じてインストールは実行されます)。最後に一度だけインストールします。
ジェネレータの出力
Section titled “ジェネレータの出力”ジェネレーターは <directory>/<api-name> ディレクトリに 2 つの関連プロジェクトを作成します:
Directorymodel/ Smithy モデルプロジェクト
- project.json プロジェクト設定とビルドターゲット
- smithy-build.json Smithy ビルド設定
- ssdk.rolldown.config.mjs 生成された TypeScript Server SDK をバンドル
Directorysrc/
- main.smithy メインサービス定義
Directoryoperations/
- echo.smithy サンプルオペレーション定義
Directorybackend/ TypeScript バックエンド実装
- package.json プロジェクトのパッケージ名と依存関係を定義するプロジェクトマニフェスト
- project.json プロジェクト設定とビルドターゲット
- rolldown.config.ts バンドル設定
- tsconfig.json TypeScript 設定
- tsconfig.lib.json ライブラリソース用の TypeScript 設定
- tsconfig.spec.json テスト用の TypeScript 設定
- vitest.config.mts Vitest 設定
Directorysrc/
- index.ts パッケージエントリーポイント
- 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 ジェネレーターで生成します:
このジェネレーターを実行@aws/nx-plugin:smithy#project
pnpm nx g @aws/nx-plugin:smithy#project yarn nx g @aws/nx-plugin:smithy#project npx nx g @aws/nx-plugin:smithy#project bunx nx g @aws/nx-plugin:smithy#project- インストール Nx Console VSCode Plugin まだインストールしていない場合
- VSCodeでNxコンソールを開く
- クリック
Generate (UI)"Common Nx Commands"セクションで - 検索
@aws/nx-plugin - smithy#project - 必須パラメータを入力
- クリック
Generate
コマンドを組み立てる7
必須
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 で呼び出し元を検索します。検索には Cognito Identity Provider クライアントを使用しますが、これは生成された Smithy バックエンドの依存関係ではないため、最初にバックエンドプロジェクトにインストールします:
pnpm add @aws-sdk/client-cognito-identity-provider@3.1126.0 --filter my-apiyarn workspace @my-scope/my-api add @aws-sdk/client-cognito-identity-provider@3.1126.0npm install --legacy-peer-deps @aws-sdk/client-cognito-identity-provider@3.1126.0 -w packages/my-api/backendbun add @aws-sdk/client-cognito-identity-provider@3.1126.0 --cwd packages/my-api/backendimport { 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),});getIdentity は ServiceContext の必須フィールドであり、上記の注意で述べたように、コンテキストは両方のエントリーポイントで構築されます — したがって src/local-server.ts にも必要です。ローカルサーバーの前には API Gateway オーソライザーがないため、ローカル開発用のスタブ ID を提供します:
const httpResponse = await serviceHandler.handle(httpRequest, { tracer, logger, metrics, getIdentity: async () => ({ sub: 'local', username: 'local' }),});これで、オペレーション内で解決された 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(),});生成されたモジュールは、API が生成されたパターンのデフォルトインテグレーションを既に定義しているため、追加の設定は必要ありません:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
tags = local.common_tags}デフォルトの isolated パターンでは、これによりオペレーションごとに 1 つの Lambda 関数が作成されます。
インテグレーションへのアクセス
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');withOverrides を介してすべてのオペレーションをオーバーライドした場合、デフォルトのルーターインテグレーションを使用するオペレーションが残っていないため、$router は使用できなくなることに注意してください。
isolated パターンでは、モジュールの出力はオペレーション名でキー付けされたマップであるため、単一のオペレーションのリソースにアクセスできます。例えば、1 つのオペレーションの Lambda 関数に追加の権限を付与するには:
# Grant additional permissions to just the sayHello operation's functionresource "aws_iam_role_policy" "say_hello_permissions" { name = "say-hello-additional-permissions" role = module.my_api.lambda_execution_role_names["sayHello"]
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "s3:GetObject", "s3:PutObject" ] Resource = "arn:aws:s3:::my-bucket/*" } ] })}すべてのオペレーションに同じ権限を付与するには、operations 出力を反復処理します:
resource "aws_iam_role_policy" "additional_permissions" { for_each = toset(module.my_api.operations)
name = "additional-api-permissions" role = module.my_api.lambda_execution_role_names[each.key]
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = ["s3:GetObject"] Resource = "arn:aws:s3:::my-bucket/*" } ] })}モジュールは、オペレーション名でキー付けされたマップとして lambda_function_names、lambda_function_arns、lambda_invoke_arns、integration_ids、lambda_log_group_names も公開します。shared パターンでは、関数が 1 つしかないため、代わりに同等の単数形の出力(lambda_execution_role_name、lambda_function_name、…)が公開されます。
すべてのオペレーションに必要な権限は、モジュールに渡す方が良いです。モジュールはそれらを各関数のロールに適用します:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
additional_iam_policy_statements = [ { Effect = "Allow" Action = ["s3:GetObject"] 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 リソースを直接編集してください。isolated パターンでは、その単一のリソースは for_each = local.operations で宣言されているため、そこでの編集はすべてのオペレーションに適用されます。
オペレーションごとのオプションのカスタマイズ
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 の両方で同じオペレーションをターゲットにすると、呼び出す順序に関係なく型エラーが発生します。
isolated パターンでは、Lambda 関数リソースは既にオペレーションごとになっているため、オプションはオペレーション名によって変更できます。例えば、1 つのオペレーションにより長いタイムアウトを与えるには、生成されたモジュール内の aws_lambda_function リソースを編集します:
resource "aws_lambda_function" "api_lambda" { for_each = local.operations
# Default to 30 seconds, but allow longer for specific operations timeout = lookup({ sayHello = 60 }, each.key, 30)
# ... rest of configuration}インテグレーションのオーバーライド
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(...);特定のオペレーションを異なるインテグレーションタイプに向けるには、デフォルトの for_each からそれを除外し、そのインテグレーションを個別に宣言します。例えば、getDocumentation を外部ウェブサイトから提供するには:
# Exclude the overridden operation from the default per-operation resourceslocals { overridden_operations = ["getDocumentation"] default_operations = { for op, details in local.operations : op => details if !contains(local.overridden_operations, op) }}
# Then use local.default_operations in place of local.operations for the# aws_lambda_function, aws_iam_role, aws_apigatewayv2_integration and# aws_lambda_permission resources, and add the override:resource "aws_apigatewayv2_integration" "get_documentation" { api_id = module.http_api.api_id integration_type = "HTTP_PROXY" integration_uri = "https://example.com/documentation" integration_method = "GET"}
resource "aws_apigatewayv2_route" "get_documentation" { api_id = module.http_api.api_id route_key = local.route_key["getDocumentation"] target = "integrations/${aws_apigatewayv2_integration.get_documentation.id}"}オーソライザーのオーバーライド
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(),});認証は各オペレーションのルート(HTTP API)またはメソッド(REST API)に設定されるため、オペレーション名によって変更できます。例えば、HTTP API で 1 つのオペレーションを認証なしのままにするには:
resource "aws_apigatewayv2_route" "operation_routes" { for_each = local.operations
# ... rest of configuration
authorization_type = each.key == "getDocumentation" ? "NONE" : "AWS_IAM"}IAM 認証された REST API の場合は、そのオペレーションのパスへの認証なしアクセスを許可するリソースポリシーステートメントも追加してください。
明示的なインテグレーション
Section titled “明示的なインテグレーション”必要に応じて、デフォルトインテグレーションを使用せず、各オペレーションに直接インテグレーションを提供することもできます。これは、例えば各オペレーションが異なるタイプのインテグレーションを使用する必要がある場合や、新しいオペレーションを追加する際に型エラーを受け取りたい場合に便利です:
new MyApi(this, 'MyApi', { integrations: { sayHello: { integration: new LambdaIntegration(...), }, getDocumentation: { integration: new HttpIntegration(...), }, },});isolated パターンで使用される for_each を、各オペレーションの Lambda 関数、インテグレーション、権限の明示的なインスタンス化に置き換えます。
インテグレーションパターン
Section titled “インテグレーションパターン”生成された API は 2 つのインテグレーションパターンをサポートしています:
isolatedは、オペレーションごとに 1 つの Lambda 関数を作成します。これは API のデフォルトで推奨されるオプションです。sharedは、単一のデフォルトルーター Lambda を作成し、特定のインテグレーションをオーバーライドしない限り、すべてのオペレーションでそれを再利用します。
isolated は、オペレーションごとにより細かい権限と設定を提供し、ログとトレースのより良い分離も提供します。shared は、使用頻度の低い API でコールドスタートに遭遇する可能性を減らします。
インテグレーションパターンは、API コンストラクトを更新することで CDK でいつでも変更できます。例えば、pattern を 'shared' に設定すると、オペレーションごとに 1 つではなく、単一の関数が作成されます:
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => { ... return IntegrationBuilder.rest({ pattern: 'shared', ... }); };}CDK とは異なり、インテグレーションパターンは生成されたモジュールに組み込まれています。インテグレーションパターンを変更するには:
packages/common/terraform/src/app/apis内の以前に生成された API モジュールを削除します- API を作成したジェネレーターを他のインテグレーションパターンで再実行します(例:
--integrationPattern=shared)
isolated パターンでは、モジュールは生成されたファイルからオペレーションを読み取ります:
locals { operations_file = "${path.module}/../../../generated/my-api/operations.json" operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}}このファイルは API から生成されるため、手動で編集する必要はありません。API アプリケーションコードにオペレーションを追加すると、次のデプロイでルートと Lambda 関数が追加されます。デフォルトでは .gitignore されています。チェックインしたい場合はエントリを削除してください。
Terraform REST API Path Depth Limit
Section titled “Terraform REST API Path Depth Limit”オペレーションは 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 ジェネレーターを使用して、このプロジェクトをワークスペース内の他のプロジェクトと統合します。このプロジェクトに関連する次の接続があります: