はじめに
プロジェクトで使用している AWS SDK を v2 から 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)処理の移行です。
変更ポイント
-
タイムアウト設定:v2 の
httpOptionsは v3 ではクライアント設定からなくなりました。代わりに@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 の.BodyはReadableStream(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操作と同様に、
PutObjectCommandやGetObjectCommandを渡す形式に統一されました。
コード比較
🟥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)への理解が一層求められるようになりました。
これから移行される方は、ぜひこの「型変換」のポイントを意識して進めてみてください!