10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【TypeScript】AWS SDK for JavaScript v2からv3への移行ガイド:S3・Lambda・Secrets Managerのコード比較

Posted at

はじめに

プロジェクトで使用している AWS SDKv2 から v3 へと移行しました。v3モジュラアーキテクチャを採用しており、必要なパッケージのみをバンドルできるため、アプリケーションのサイズの削減やTypeScriptとの親和性向上が期待できます!

しかし、単なるメソッドの書き換えでなく、「型定義」「ストリーム処理」 など、実装方法が大きく変わる部分があり、いくつかつまずくポイントがありました。そこで、この記事では、AWS SDK v2 と v3 の具体的なコードを比較しながら、簡単に解説をしていきたいと思います!

この記事の要点

  • Secrets Manager:基本設定不要だが、ローカル実行時はプロファイル指定のために fromNodeProviderChain が便利
  • Lambda Invoke:Payload / Response が Uint8Array になるため、TextEncoder での変換が必須
  • S3 GetObject:Body が ReadableStream で返るため、transformToByteArray 等で Buffer への変換が必要
  • S3 署名付きURL@aws-sdk/s3-request-presigner を別途インポートし、getSignedUrl が非同期に変更

この記事の対象者

  • AWS SDK for JavaScript v2 から v3 への移行を検討している方
  • TypeScript / Node.js 環境で AWS SDK を利用している方
  • v3 移行後に「Payloadの型が合わない」「Bodyが空になる」等のエラーで困っている方

動作環境

  • Node.js v18.20.2
  • TypeScript v5.4.5
  • AWS SDK for JavaScript v3 (@aws-sdk/client-*)

全体的な変更点

v3 の最大の特徴は先ほども述べた通り 「モジュラアーキテクチャ」 です。aws-sdk 全体を読み込むのではなく、必要なクライアントとコマンドだけ をインポートします。
また、v3 では「ミドルウェアスタック」が導入されており、リトライ処理やログ出力などを柔軟にカスタマイズできるようになっています(本記事では詳細は割愛します)。

v2(旧) v3(新)
インポート import AWS from 'aws-sdk' import { S3Client, ... } from '@aws-sdk/client-s3'
実行方法 .promise() .send(command)
戻り値 Promise(.promise() で取得) Promise(標準)

これに伴い、v2 で必要だった .promise() は不要になります。詳細な移行ガイドラインについては、AWS公式ドキュメントも併せて参照してください。

参考: Migrating your code to SDK for JavaScript v3 - AWS SDK for JavaScript

1. Secrets Manager (認証プロバイダーの指定)

DB接続情報などを取得する処理の移行です。

変更ポイント

  • 認証プロバイダーのモジュール化v3 では認証情報の取得方法も個別のパッケージに分かれています。 
  • デフォルトの挙動:通常は何も設定しなくても、v2 と同様にデフォルトの認証チェーン(環境変数 → 設定ファイル/プロファイルなど)が自動的に働きます。
  • 明示的な指定:ローカル開発環境で「特定のプロファイルを強制的に読み込ませたい」場合や、認証エラーが起きる際の切り分け(デバッグ)として、@aws-sdk/credential-providers を利用して明示的に記載する手法を知っておくと非常に役立ちます。

コード比較

🟥Before(v2)

import AWS from 'aws-sdk';

// v2: 認証情報はデフォルトでチェーン検索される
const client = new AWS.SecretsManager({ region: 'ap-northeast-1' });

export const getSecret = async (secretId: string) => {
  try {
    const data = await client.getSecretValue({ SecretId: secretId }).promise();
    return JSON.parse(data.SecretString || '{}');
  } catch (error) {
    console.error(error);
    return {};
  }
};

🟩After(v3)

import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
// 認証プロバイダーが必要な場合は個別にインポート
import { fromNodeProviderChain } from '@aws-sdk/credential-providers';

const client = new SecretsManagerClient({ 
  region: 'ap-northeast-1',
  // 省略するとデフォルトのチェーンが使われます。
  // ローカル開発でプロファイルを明示したい場合などに指定します。
  // credentials: fromNodeProviderChain() 
});

export const getSecret = async (secretId: string) => {
  const command = new GetSecretValueCommand({ SecretId: secretId });
  
  try {
    const data = await client.send(command);
    if (data.SecretString) {
      return JSON.parse(data.SecretString);
    }
  } catch (error) {
    console.error(error);
  }
  return {};
};

2. Lambda(Payloadの型とタイムアウト)

サーバー側から別の Lambda 関数を呼び出す(Invoke)処理の移行です。

変更ポイント

  • タイムアウト設定v2httpOptionsv3 ではクライアント設定からなくなりました。代わりに@smithy/node-http-handler をインポートし、ハンドラーを作成して渡す必要があります。
    • ※以前は @aws-sdk/node-http-handler が使われていましたが、現在は非推奨となり、@smithy パッケージへの移行が推奨されています。
  • Payloadの型v2 ではJSON文字列でOKだったのですが、v3 ではUint8Array(バイナリ)である必要があります。そのため、TextEncoder で変換しないと型エラーになります。

コード比較

🟥Before(v2)

import AWS from 'aws-sdk';

const lambda = new AWS.Lambda({
  region: 'ap-northeast-1',
  httpOptions: { timeout: 300000 } // 5分 (簡単に設定できた)
});

const invokeLambda = async (payload: any) => {
  return await lambda.invoke({
    FunctionName: 'target-function',
    InvocationType: 'RequestResponse',
    Payload: JSON.stringify(payload) // 文字列をそのまま渡せた
  }).promise();
};

🟩After(v3)

import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda';
// 最新のSDK仕様に合わせて @smithy パッケージを使用
import { NodeHttpHandler } from '@smithy/node-http-handler';

// タイムアウト設定には専用のハンドラが必要
const requestHandler = new NodeHttpHandler({
  requestTimeout: 300000,   // 5分
  connectionTimeout: 300000 // 5分
});

const lambda = new LambdaClient({
  region: 'ap-northeast-1',
  requestHandler // ここで設定
});

const invokeLambda = async (payload: any) => {
  // 【重要】文字列を Uint8Array にエンコードする
  const payloadUint8 = new TextEncoder().encode(JSON.stringify(payload));

  const command = new InvokeCommand({
    FunctionName: 'target-function',
    InvocationType: 'RequestResponse',
    Payload: payloadUint8
  });

  try {
    const response = await lambda.send(command);

    // レスポンスも Uint8Array なのでデコードが必要
    if (response.Payload) {
      const resString = new TextDecoder().decode(response.Payload);
      return JSON.parse(resString);
    }
  } catch (error) {
    console.error("Lambda Invoke Error:", error);
    throw error;
  }
};

3. S3(レスポンスボディのストリーム処理)

画像の取得(GetObject)処理などの移行です。

変更ポイント

  • ストリーム処理v2 では、.Body がそのまま as Buffer でキャストできましたが、v3.BodyReadableStream (Node.jsでは IncomingMessage )で返ってきます。
    • そのため、v2と同じ感覚でキャストすると動きません。
    • これを Buffer に戻すためには transformToByteArray(推奨)やsdkStreamMixin を使います。

コード比較

🟥Before(v2)

import AWS from 'aws-sdk';
const s3 = new AWS.S3();

const downloadFile = async (bucket: string, key: string) => {
  const data = await s3.getObject({ Bucket: bucket, Key: key }).promise();
  // v2ではそのまま Buffer として扱えた
  return data.Body as Buffer;
};

🟩After(v3)

import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';

const s3 = new S3Client({ region: 'ap-northeast-1' });

const downloadFile = async (bucket: string, key: string) => {
  const command = new GetObjectCommand({ Bucket: bucket, Key: key });
  const response = await s3.send(command);

  if (!response.Body) return undefined;

  // 【推奨】v3のBodyはストリームですが、transformToByteArray() メソッドを持っています
  // Uint8Array で取得できるので Buffer に変換
  const byteArray = await response.Body.transformToByteArray();
  return Buffer.from(byteArray);

  /* // 参考: sdkStreamMixin を使う場合(古いバージョンなど)
  // ※ @aws-sdk/util-stream-node は deprecated のため @smithy を推奨
  // import { sdkStreamMixin } from '@smithy/util-stream';
  // const str = await sdkStreamMixin(response.Body).transformToString('base64');
  // return Buffer.from(str, 'base64');
  */
};

4. S3(署名付きURLの生成)

S3へのアップロードやダウンロード用URLを一時的に発行する、署名付きURL生成処理の移行です。

変更ポイント

  • 別パッケージが必要v3 では S3クライアントのメソッドではなく、独立したパッケージ @aws-sdk/s3-request-presigner を使用します。
  • 非同期処理に変更v2 では同期的に呼び出せましたが、v3 では await が必要になります。
  • コマンドパターン:他のS3操作と同様に、PutObjectCommandGetObjectCommand を渡す形式に統一されました。

コード比較

🟥Before(v2)

import AWS from 'aws-sdk';

const s3 = new AWS.S3({ region: 'ap-northeast-1' });

// v2: 同期的に呼び出せた
const getUploadUrl = (bucket: string, key: string) => {
  return s3.getSignedUrl('putObject', {
    Bucket: bucket,
    Key: key,
    Expires: 300,
    ContentType: 'application/pdf'
  });
};

🟩After(v3)

import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3 = new S3Client({ region: 'ap-northeast-1' });

// v3: 非同期になった点に注意
const getUploadUrl = async (bucket: string, key: string) => {
  const command = new PutObjectCommand({ 
    Bucket: bucket, 
    Key: key,
    ContentType: 'application/pdf'
  });

  // 【注意】await が必要
  return await getSignedUrl(s3, command, { expiresIn: 300 });
};

その他補足

今回は3つのサービスに絞って紹介しましたが、DynamoDB, SNS, SQS など他のサービスクライアントでも、基本的には 「Clientの生成 + Commandの作成 + send()」 という同じ構造になります。
より詳細な仕様については以下の公式ドキュメントなどを参照してください。

参考資料

おわりに

AWS SDK v3 への移行は、TypeScript の型定義の恩恵を最大限に受けられるアップデートです。
一方で、「Payloadの型変換」「ストリーム処理」「署名付きURLの非同期化」 など、Node.js の標準 API(TextEncoder/Decoder, Stream)への理解が一層求められるようになりました。

これから移行される方は、ぜひこの「型変換」のポイントを意識して進めてみてください!

10
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?