---
title: "AWS SDK for JavaScript v3とは？使い方・v2からの移行手順とS3・DynamoDB実装例"
url: "https://www.issoh.co.jp/tech/details/6814/"
published: 2025-05-20
updated: 2026-10-14
categories: ["AWS"]
publisher: "株式会社一創"
---

# AWS SDK for JavaScript v3とは？使い方・v2からの移行手順とS3・DynamoDB実装例

AWS SDK for JavaScript v3は、Node.jsやブラウザからAWSのAPIを呼ぶための公式ライブラリです（[AWS公式の開発者ガイド](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/welcome.html)）。v2（npmパッケージ名 aws-sdk）は2025年9月8日にサポートが終了しており、いま新規に書くコードも、既存コードの移行先もv3一択です。

この記事では、v3の導入からS3・DynamoDBの基本操作、v2からの移行手順、エラー処理までを、ローカルで実行した結果を添えて説明します。移行ツールで変換しても直らない箇所、S3互換ストレージで突然アップロードが失敗する既定値の変更など、公式ガイドを順に読むだけでは気づきにくい落とし穴を中心に扱います。検証の実行環境はNode.js v26.5.0、@aws-sdk/client-s3 3.1132.0（2026年9月15日時点のnpm最新）です。2026年10月14日時点のnpm最新は3.1151.0で、本文の手順はこの版でもAPIの形が変わりません。

## まとめ：AWS SDK for JavaScript v3で押さえるべき要点と移行判断

- v2（aws-sdk）は2024年9月8日にメンテナンスモード入り、2025年9月8日にサポート終了。npmでインストールすると非推奨の警告が出ます。
- v3はサービスごとに @aws-sdk/client-s3 のような個別パッケージを入れ、クライアントの send() にコマンドを渡して呼び出します。.promise() は不要です。
- 2026年10月時点のv3はNode.js 20以上が必須です。v3によるNode.js 20のサポートは2027年1月に終了する予定で、Node.js 20自体は2026年4月30日にEOLを迎えています。
- v2からの移行は aws-sdk-js-codemod で大半を機械変換できますが、S3 GetObject の Body.toString() は変換されず、実行すると \[object Object\] が返ります。
- v3.729.0（2025年1月15日公開）以降、S3へのアップロードにCRC32チェックサムが既定で付きます。非対応のS3互換ストレージでは requestChecksumCalculation を WHEN\_REQUIRED にします。
- DynamoDBの DocumentClient は、ネストしたオブジェクトや配列の中に undefined があるとエラーになります。removeUndefinedValues: true で回避します。
- エラーは例外クラス（NoSuchKey など）を instanceof で判定でき、HTTPステータスは $metadata.httpStatusCode で取れます。メッセージ文字列での分岐は避けます。

## AWS SDK for JavaScript v3とは：v2との違いと公式資料の全体像

v3は2020年12月にv3.0.0がnpmへ公開された、v2の全面的な書き直しです。サービス単位のパッケージ分割、コマンドオブジェクトによる呼び出し、ミドルウェアスタックによる拡張の3点が、v2のコードとの見た目の差として現れます。ソースコードはTypeScriptで書かれ、[GitHubのaws/aws-sdk-js-v3リポジトリ](https://github.com/aws/aws-sdk-js-v3)で公開されています。SDKそのものの位置づけ（APIやライブラリとの違い）は[SDKとは？API・ライブラリとの違いを比較](/column/details/5278/)で整理しました。

### パッケージ構成と呼び出し方の違いをv2とv3のコード表で比較する

| 項目          | v2                    | v3                  |
| ----------- | --------------------- | ------------------- |
| パッケージ       | aws-sdk（全サービス同梱）      | client-s3 など個別      |
| 呼び出し        | getObject().promise() | send(new Command()) |
| 全体設定        | AWS.config.update()   | なし（クライアントごと）        |
| S3の本文       | Buffer                | ストリーム               |
| 拡張点         | イベントリスナー              | ミドルウェアスタック          |
| TypeScript型 | 同梱                    | 各クライアントに同梱          |

表の「呼び出し」は、v2が s3.getObject(params).promise()、v3が client.send(new GetObjectCommand(params)) の形です。S3の本文はv3では transformToString などで読みます。v3にもv2風の書き方ができる集約クライアント（new S3() と s3.getObject()）が残っていますが、公式が基本形として示すのは S3Client と個別コマンドの組み合わせです。ブラウザ向けでは後述のとおりバンドルサイズに差が出ます。

### 開発者ガイド・APIリファレンス・GitHubを目的別に読み分ける

v3の公式資料は3系統に分かれており、知りたい内容で開く場所が変わります。

| 知りたいこと      | 開く資料         |
| ----------- | ------------ |
| 導入手順・設定の考え方 | 開発者ガイド       |
| コマンドの引数と戻り値 | APIリファレンス    |
| v2からの書き換え   | 移行ガイド        |
| 既定値の変更・不具合  | GitHubのIssue |

コマンドごとの入力項目は[v3のAPIリファレンス](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/)で、クライアント名からたどれる構成です。v2との対応表は[公式の移行ガイド](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/migrating.html)にあります。一方、後述するチェックサム既定値の変更のように、挙動の変わり目はGitHubのIssueで告知されることが多いため、版を上げる前にリポジトリの告知も確認します。

### v2のサポート終了日とv3が要求するNode.jsの版を確認する

v2は2024年9月8日にメンテナンスモードへ入り、重大な不具合とセキュリティの修正だけを受ける期間を経て、2025年9月8日にサポートが終了しました（[AWS Developer Tools Blogのサポート終了告知](https://aws.amazon.com/blogs/developer/announcing-end-of-support-for-aws-sdk-for-javascript-v2/)）。AWSの告知では、既存アプリケーションはAWS側のサービスに根本的な変更がない限り動き続けるとしていますが、新しいサービスや新機能には追随しません。npmの aws-sdk 2.1693.0 にも、サポート終了を示す deprecated の表示が付いています。

v3はNode.jsのリリーススケジュールに合わせてサポート対象を決めています。AWSの方針は、全LTS版でテストし、直近にEOLを迎えた版だけ追加で8か月サポートするというものです（[AWS Developer Tools Blogの告知](https://aws.amazon.com/blogs/developer/aws-sdk-for-javascript-aligns-with-node-js-release-schedule/)）。2027年以降の日付は、この告知の表でもNode.jsのリリーススケジュールに基づく見込みとされています。

| Node.js | Node.js自体のEOL | v3のサポート終了             |
| ------- | ------------- | --------------------- |
| 18.x    | 2025-04-30    | 2026-01-13の3.968.0で終了 |
| 20.x    | 2026-04-30    | 2027年1月（予定）           |
| 22.x    | 2027-04-30    | 2028年1月（予定）           |
| 24.x    | 2028-04-30    | 2029年1月（予定）           |

npmの @aws-sdk/client-s3 は、3.1132.0 も2026年10月14日時点の最新 3.1151.0 も、package.json の engines に node >=20.0.0 を宣言しています。Node.js 20でビルドしているCIは、本体がすでにEOLを迎えているため、SDK側の対応終了を待たず、サポート中のNode.js 22または24へ移行します。Node.jsの版の選び方は[Node.jsとは？仕組み・版の選び方・標準機能で動かす手順](/tech/details/17433/)を参照してください。

## v3のインストールとS3Clientで最初のAPIを呼び出す手順

### npm installでclient-s3を入れてS3Clientを生成する

使うサービスのクライアントだけをインストールします。S3なら次の1パッケージです。

```
npm install @aws-sdk/client-s3
```

バケット内のオブジェクトを最大10件表示するコードです。index.mjsとして保存し、Bucketを実在するバケット名へ変更します。対象バケットへのs3:ListBucket権限を持つ認証情報を設定し、node index.mjsで実行します。

```
import { S3Client, ListObjectsV2Command } from "@aws-sdk/client-s3";

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

const res = await s3.send(
  new ListObjectsV2Command({ Bucket: "my-bucket", MaxKeys: 10 })
);
for (const obj of res.Contents ?? []) {
  console.log(obj.Key, obj.Size);
}
```

クライアントはリクエストごとに作らず、モジュールの先頭で1つ作って使い回します。v3のクライアントは既定で最大3回の試行（maxAttempts: 3、retryMode: standard）を行うので、単発の通信エラーで自前のリトライを重ねる必要はありません。実測でも client.config.maxAttempts() は 3、retryMode() は standard を返しました。トップレベルの await はESモジュール（.mjs）でだけ書けるので、CommonJSのファイルでは async 関数の中で呼びます。await の書き方は[非同期関数の書き方｜async宣言・await式・TypeScriptの型定義](/tech/details/3794/)で整理しています。

### 認証情報の読み込み順と「Could not load credentials」の対処

credentials を指定しないと、Node.jsでは @aws-sdk/credential-provider-node の既定チェーンが上から順に認証情報を探します（[開発者ガイドの認証情報の設定](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html)）。3.1132.0 のソースで確認した順序は次のとおりです。

1. 環境変数（AWS\_ACCESS\_KEY\_ID と AWS\_SECRET\_ACCESS\_KEY）
2. SSO（クライアント生成時にSSOの項目を明示した場合のみ）
3. 共有設定ファイル（\~/.aws/credentials と \~/.aws/config。SSOプロファイルもここで読む）
4. credential\_process で指定した外部コマンド
5. Web Identityトークンファイル（EKSのIRSAなど）
6. コンテナ認証情報用の環境変数がある場合はコンテナのエンドポイント、ない場合はEC2インスタンスメタデータ（無効化されていない場合）

@aws-sdk/credential-providers のAPIリファレンスは、Web Identityトークンを共有設定ファイルより先に書いていますが、実装では共有設定ファイルと credential\_process が先に評価されます。\~/.aws/config とトークンファイルの両方がある環境では、実装の順序で考えてください。

見落としやすいのは、環境変数 AWS\_PROFILE が設定されていると、アクセスキーの環境変数があっても1番目が飛ばされる点です。両方を設定した場合はプロファイル側が使われ、Multiple credential sources detected という警告が出ます。CIで一時的にキーを環境変数に入れても別アカウントのプロファイルで動いてしまう、という事故はこれが原因です。

どこにも認証情報がないと、send() は CredentialsProviderError を投げ、メッセージは Could not load credentials from any providers になります。HOMEを空にした環境で実行して、このメッセージを確認しました。ローカル開発ではアクセスキーを置かず、[aws loginコマンドの実行手順とsso login・アクセスキーとの使い分け](/tech/details/17567/)で説明しているSSOのプロファイルを使うのが安全です。AWS CLIの設定ファイルの作り方は[AWS CLIの導入と運用](/tech/details/17403/)にまとめています。

## S3とDynamoDBの基本操作をv3のコマンドで実装するコード例

### S3 GetObjectの本文取得とtransformToStringによる文字列化

v3の GetObject が返す Body はストリームです。文字列として読むには transformToString()、バイト列なら transformToByteArray() を使います。

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

const s3 = new S3Client({ region: "ap-northeast-1" });
const res = await s3.send(
  new GetObjectCommand({ Bucket: "cfg", Key: "config.json" })
);
const config = JSON.parse(await res.Body.transformToString());
```

v2の感覚で res.Body.toString() と書くと、エラーにならず \[object Object\] という文字列が返ります。JSON.parse の段階で初めて失敗するため、原因がSDKの移行にあると気づきにくい箇所です。S3の権限設計や署名付きURLは[AWS S3の使い方](/tech/details/17489/)で扱っています。

### 大きなファイルはlib-storageのUploadで分割アップロードする

実行前にnpm install @aws-sdk/lib-storageで追加パッケージを入れ、読み込むdb.dumpを用意します。

PutObjectCommand は1リクエストで送るため、数百MBを超えるファイルやサイズが事前に分からないストリームには向きません。@aws-sdk/lib-storage の Upload はマルチパートアップロードを自動で組み立てます。

```
import { S3Client } from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
import { createReadStream } from "node:fs";

const upload = new Upload({
  client: new S3Client({ region: "ap-northeast-1" }),
  params: {
    Bucket: "my-bucket",
    Key: "backup/db.dump",
    Body: createReadStream("./db.dump"),
  },
  queueSize: 4,
  partSize: 8 * 1024 * 1024,
});
upload.on("httpUploadProgress", (p) => console.log(p.loaded, p.total));
await upload.done();
```

lib-storage のソースでは並列数 queueSize の既定値が4、パートの最小サイズが5MB（1024 \* 1024 \* 5）です。帯域が細い環境では queueSize を下げ、パートサイズを上げてリクエスト数を減らします。

### DynamoDBはlib-dynamodbとundefined除去の設定で扱う

実行前にnpm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodbで依存を追加します。usersテーブルには文字列型のパーティションキーpkを用意し、実行する認証情報にdynamodb:PutItemとdynamodb:GetItemを許可します。

DynamoDBの低レベルAPIは `{ S: "taro" }` のような型付きの形式を要求します。@aws-sdk/lib-dynamodb の DynamoDBDocumentClient を挟むと、普通のJavaScriptオブジェクトのまま読み書きできます。

```
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import {
  DynamoDBDocumentClient,
  PutCommand,
  GetCommand,
} from "@aws-sdk/lib-dynamodb";

const doc = DynamoDBDocumentClient.from(
  new DynamoDBClient({ region: "ap-northeast-1" }),
  { marshallOptions: { removeUndefinedValues: true } }
);

await doc.send(new PutCommand({
  TableName: "users",
  Item: { pk: "user#1", profile: { nickname: undefined }, tags: ["a"] },
}));
const { Item } = await doc.send(
  new GetCommand({ TableName: "users", Key: { pk: "user#1" } })
);
```

removeUndefinedValues を付けない場合の挙動を、通信をしないダミーのハンドラで確かめた結果です。表の「オプション有効」は removeUndefinedValues: true を指定した場合です。

| Itemの形             | 既定        | オプション有効          |
| ------------------ | --------- | ---------------- |
| 直下の項目が undefined   | 成功（属性を省略） | 成功（属性を省略）        |
| ネストした項目が undefined | エラー       | 成功（profileは空のM）  |
| 配列の要素が undefined   | エラー       | 成功（tagsは`["a"]`） |

試した入力は、直下が `{ pk, nickname: undefined }`、ネストが `{ pk, profile: { nickname: undefined } }`、配列が `{ pk, tags: ["a", undefined] }` です。エラー時のメッセージは Pass options.removeUndefinedValues=true to remove undefined values from map/array/set. です。トップレベルの undefined は通るため、単体テストで平らなオブジェクトしか使っていないと、本番でネストした入力が来て初めて落ちます。フォームの任意項目をそのまま保存するコードでは最初から有効にしておきます。テーブル設計側の考え方は[DynamoDBとは：ローカル起動からキー設計・容量モード選定までの実装手順](/tech/details/17453/)を参照してください。

### 一覧取得のページングはpaginate関数とfor awaitで回す

ListObjectsV2 は1回で最大1,000件しか返しません。v3には各サービスに paginate で始まる関数があり、for await で全ページを回せます。

```
import { S3Client, paginateListObjectsV2 } from "@aws-sdk/client-s3";

const s3 = new S3Client({ region: "ap-northeast-1" });
let total = 0;
for await (const page of paginateListObjectsV2(
  { client: s3 },
  { Bucket: "my-bucket", Prefix: "logs/" }
)) {
  total += page.KeyCount ?? 0;
}
console.log(total);
```

作成直後のリソースを待つ処理には waitUntil で始まる関数（S3なら waitUntilObjectExists）が用意されています。v2の waitFor() の置き換えです。

## v3のエラー処理：例外クラスとhttpStatusCodeで失敗を判定する

v3では、APIが返したエラーはサービスごとの例外クラスとして投げられます。S3なら S3ServiceException が基底クラスで、NoSuchKey などの個別のエラーがそれを継承する構造です。v2のように err.code の文字列で分岐する書き方は、v3では err.name と instanceof へ置き換えます。

```
import {
  S3Client,
  GetObjectCommand,
  NoSuchKey,
  S3ServiceException,
} from "@aws-sdk/client-s3";

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

export async function readOrNull(bucket, key) {
  try {
    const res = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
    return await res.Body.transformToString();
  } catch (err) {
    if (err instanceof NoSuchKey) return null;
    if (err instanceof S3ServiceException) {
      console.error(err.name, err.$metadata.httpStatusCode, err.$metadata.requestId);
    }
    throw err;
  }
}
```

$metadata にはHTTPステータスのほか、AWSサポートへの問い合わせで使う requestId と、リトライ込みの試行回数 attempts が入ります。ログにはメッセージ文字列だけでなく name と requestId を残しておくと、障害調査でAWS側の記録と突き合わせられます。

注意点として、HeadObject のように本文を返さないAPIでは、存在しないキーに対して NoSuchKey ではなく name が NotFound のエラーになります。HEADリクエストにはエラーの本文が無く、SDKがエラーコードを読み取れないためです。存在確認を HeadObject で書く場合は、err.name === “NotFound” または err.$metadata.httpStatusCode === 404 で判定します。

### TypeScriptで書くときの型の効き方とunknown型のエラー処理

v3の各クライアントは型定義を同梱しているため、TypeScriptではコマンドの入力と出力に型が付きます。GetObjectCommand の入力で Bucket を書き忘れると、実行前にコンパイルエラーとなり、型の不一致に気づけるのが利点です。一方、catch 句の err は unknown 型として扱われる設定（useUnknownInCatchVariables、strict に含まれる）が多く、上のコードのように instanceof で絞り込んでからプロパティを読む書き方がそのまま型の絞り込みとして働きます。型の基本は[TypeScriptとは？型・型推論・型注釈の書き分けとJavaScriptとの違い](/tech/details/2815/)で扱いました。

## v2からv3への移行手順：codemodの変換範囲と手直し箇所

### aws-sdk-js-codemodで機械変換できる範囲を実行して確かめる

AWSが公開している[aws-sdk-js-codemod](https://github.com/aws/aws-sdk-js-codemod)は、v2のコードをv3の書き方へ書き換えるツールです。次のv2のコードを 3.0.2（2026年10月時点でもnpmの最新）で変換しました。

```
const AWS = require("aws-sdk");
const s3 = new AWS.S3({ region: "ap-northeast-1" });
async function main() {
  const obj = await s3.getObject({ Bucket: "cfg", Key: "config.json" }).promise();
  console.log(obj.Body.toString());
  const ddb = new AWS.DynamoDB.DocumentClient();
  await ddb.put({ TableName: "users", Item: { pk: "u#1" } }).promise();
}
main();
```

```
npx aws-sdk-js-codemod@3.0.2 -t v2-to-v3 app.js
```

変換後のコードは次のようになりました。

```
const { DynamoDBDocument } = require("@aws-sdk/lib-dynamodb");
const { DynamoDB } = require("@aws-sdk/client-dynamodb");
const { S3 } = require("@aws-sdk/client-s3");

const s3 = new S3({
  region: "ap-northeast-1"
});
async function main() {
  const obj = await s3.getObject({ Bucket: "cfg", Key: "config.json" });
  console.log(obj.Body.toString());
  const ddb = DynamoDBDocument.from(new DynamoDB());
  await ddb.put({ TableName: "users", Item: { pk: "u#1" } });
}
main();
```

require の差し替え、.promise() の削除、DocumentClient から DynamoDBDocument への置き換えまでは自動で済みました。一方で、生成されるのはv2風の集約クライアント（S3、DynamoDB）で、S3Client と個別コマンドの形にはなりません。

### codemodの変換後に手で直す3か所と段階的に置き換える進め方

codemodの出力をそのまま動かすと、次の3か所で問題が残ります。

1. **obj.Body.toString()**：変換されずに残ります。変換後のコードを実行すると \[object Object\] が出力されました。await obj.Body.transformToString() に書き換えます。
2. **AWS.config.update() による全体設定**：v3には全体設定がありません。リージョンやリトライ回数は、各クライアントのコンストラクタに渡すか、共通の設定オブジェクトを作って各クライアントへ展開します。
3. **ブラウザ向けの集約クライアント**：Node.jsのサーバーでは集約クライアントのままで困りませんが、ブラウザへ配るコードは S3Client と必要なコマンドだけのimportに直すとバンドルが小さくなります（後の章で実測）。

加えて、エラー処理で err.code を見ている箇所は、前章のとおり err.name か instanceof に直します。codemodはこの分岐を書き換えないため、grep で「.code ===」を探して洗い出します。

v2とv3はパッケージ名が異なるため、同じプロジェクトに共存できます。今回の検証でも aws-sdk 2.1693.0 と @aws-sdk/client-s3 3.1132.0 を同じ node\_modules に入れて動かしました。モジュール単位でv3に置き換え、テストが通るたびにv2の呼び出しを減らしていく進め方が取れます。

## S3互換ストレージでアップロードが失敗するチェックサム既定値の対処

v3.729.0（2025年1月15日にnpmへ公開）から、S3クライアントは既定でアップロードにチェックサムを付け、ダウンロード時に検証するようになりました。この変更は[GitHubのIssue 6810（S3 default integrity change）](https://github.com/aws/aws-sdk-js-v3/issues/6810)で告知されています。AWSのSDK共通リファレンスの[S3の整合性保護のページ](https://docs.aws.amazon.com/sdkref/latest/guide/feature-dataintegrity.html)でも、SDK for JavaScript 3.x の既定アルゴリズムはCRC32と記載されています。3.1132.0 で PutObjectCommand が送るリクエストヘッダーを確かめると、x-amz-sdk-checksum-algorithm と x-amz-checksum-crc32 の2つが付いていました。

AWSのS3では問題になりませんが、この新しいヘッダーに対応していないS3互換ストレージではアップロードが失敗します。Cloudflare R2のコミュニティでは、3.729.0 から PutObject と UploadPart が 501 NotImplemented で失敗するという報告が上がりました。MinIOなどでも同種の報告があります。SDKの版を上げただけでアップロードが失敗し始めたら、まずこの変更を疑います。

対処は、チェックサムを必要なAPIでだけ付ける WHEN\_REQUIRED への切り替えです。

```
const s3 = new S3Client({
  region: "auto",
  endpoint: "https://ACCOUNT_ID.r2.cloudflarestorage.com",
  requestChecksumCalculation: "WHEN_REQUIRED",
  responseChecksumValidation: "WHEN_REQUIRED",
});
```

コードを変えられない場合は、環境変数 AWS\_REQUEST\_CHECKSUM\_CALCULATION=when\_required でも送信側を変更できます。上のコードと同様に受信側も変更するには、AWS\_RESPONSE\_CHECKSUM\_VALIDATION=when\_required を併せて設定します。コンストラクタ指定と環境変数のそれぞれで、チェックサムのヘッダーが消えることを確認しました。AWSのS3だけを使うなら既定値のままにしておくべきで、この設定を一律に入れると転送中の破損を検出する仕組みを自分で外すことになります。

## ミドルウェアスタックの5ステップ構造と独自処理を追加する方法

v3のクライアントは、1回のAPI呼び出しを initialize、serialize、build、finalizeRequest、deserialize の5つのステップに分けたミドルウェアの列で処理します。S3Client の PutObjectCommand について middlewareStack.identify() で並びを出力すると、次の順でした（抜粋）。

| ステップ            | 主なミドルウェア               | 役割              |
| --------------- | ---------------------- | --------------- |
| initialize      | バケット名の検証               | 入力の検証           |
| serialize       | serializerMiddleware   | 入力をHTTPリクエストへ変換 |
| build           | contentLength ほか       | ヘッダーの付与         |
| finalizeRequest | retry、httpSigning      | リトライと署名         |
| deserialize     | deserializerMiddleware | レスポンスを出力へ変換     |

表のミドルウェア名は略記で、実際の名前は validateBucketNameMiddleware、contentLengthMiddleware、retryMiddleware、httpSigningMiddleware です。置くステップによって、リトライ時に実行される回数が変わります。常に503を返すダミーのハンドラでHeadBucketを呼び、3回試行させたときの実行回数は、initialize が1回、build が1回、finalizeRequest が3回でした。リトライ処理が finalizeRequest の中で次のミドルウェアを繰り返し呼ぶためです。priority を指定せず add() した場合、試行ごとのログを取りたいなら finalizeRequest、所要時間をリトライ込みで測りたいなら initialize に置きます。priority や addRelativeTo で順序を変えると回数も変わるので、その場合は同じ方法で実行回数を確かめてください。

```
s3.middlewareStack.add(
  (next, context) => async (args) => {
    const start = Date.now();
    try {
      return await next(args);
    } finally {
      console.log(context.commandName, Date.now() - start, "ms");
    }
  },
  { step: "initialize", name: "timingMiddleware" }
);
```

name を付けておくと `middlewareStack.remove("timingMiddleware")` で外せます。リクエストヘッダーを書き換えたい場合は build に置き、args.request.headers を編集します。リトライの間隔やジッターの考え方は[リトライパターンとは？指数バックオフ・ジッター・リトライ予算の実装判断](/tech/details/16185/)で解説しました。

## LambdaとブラウザでAWS SDK for JavaScript v3を使う注意点

### Lambda同梱SDKのバージョン確認と依存関係を固定する手順

LambdaのNode.jsランタイム（nodejs22.x、nodejs24.x、nodejs26.x）にはv3が同梱されており、依存に含めなくても import できます。ただし[LambdaのNode.jsランタイムのドキュメント](https://docs.aws.amazon.com/lambda/latest/dg/lambda-nodejs.html)によると、同梱されるのはv3の特定のマイナー版で、最新版ではなく、版はランタイムとリージョンによって異なります。v2はどのランタイムにも含まれません。

同梱版を確認するには、次のハンドラを実行します（Lambdaのドキュメントに掲載されている方法です）。

```
import packageJson from "@aws-sdk/client-s3/package.json" with { type: "json" };

export const handler = async () => ({ version: packageJson.version });
```

ローカルで使った版より古いSDKが本番で動くと、新しいパラメーターが無視されたり、前述のチェックサム既定値のように挙動が変わったりします。新しい機能に依存するコードや、版を固定したい本番環境では、SDKを依存に入れてバンドルします。Lambdaの仕組みや料金は[AWS Lambdaとは？仕組み・料金体系とコールドスタート対策・採用判断](/tech/details/15407/)を参照してください。

### ブラウザ向けバンドルサイズをesbuildでv2とv3を比べて実測

esbuild 0.28.2 で、ブラウザ向けに minify したESMバンドルのサイズを測りました。各ファイルはクライアント（とコマンド）をimportし、console.logで参照する最小構成です。esbuildには`--bundle --minify --platform=browser --format=esm`を指定し、生成ファイルのバイト数とgzip -cの出力バイト数を測りました。

| import内容             | minify後     | gzip後     |
| -------------------- | ----------- | --------- |
| v3 S3Client＋Put      | 261,657 B   | 82,362 B  |
| v3 集約クライアント S3       | 281,443 B   | 86,862 B  |
| v2 S3のみ              | 427,681 B   | 106,699 B |
| v2 aws-sdk（全体）       | 3,392,359 B | 470,048 B |
| v3 DynamoDB＋Document | 178,405 B   | 58,009 B  |
| v2 DynamoDBのみ        | 337,090 B   | 95,433 B  |

表の「Put」は PutObjectCommand、「Document」は DynamoDBDocumentClient、「v2 S3のみ」「v2 DynamoDBのみ」は aws-sdk/clients/s3 と aws-sdk/clients/dynamodb を個別にimportした場合です。v2で aws-sdk 全体を読み込んでいたコードなら、v3への移行でgzip後のサイズは約6分の1になります。v2でもサービス単位でimportしていた場合の差はS3で約23%にとどまり、S3クライアントだけで80KB（gzip後）を超える点は変わりません。ブラウザから大きなファイルを扱う用途なら、SDKを配らずにサーバー側で[署名付きURL](/tech/details/17489/)を発行し、ブラウザは fetch で直接アップロードする構成のほうが軽くなります。バンドラーの設定は[esbuildとは？Go製の高速バンドラの使い方と採用判断](/tech/details/16017/)を参照してください。

ブラウザでSDKを使う場合、アクセスキーをコードに埋め込んではいけません。@aws-sdk/credential-providers の fromCognitoIdentityPool でCognitoのIDプールから一時的な認証情報を受け取り、S3バケット側にはCORSの設定を入れます。Cognito側の構築は[Cognitoで認証基盤を実装する手順](/tech/details/17445/)で説明しています。

## aws-sdk-client-mockでAWSへ通信せずに単体テストを書く方法

SDKを呼ぶ関数のテストでは、AWSへの通信をさせずに応答を差し替えます。コミュニティ製の[aws-sdk-client-mock](https://github.com/m-radzikowski/aws-sdk-client-mock)を使うと、クライアントとコマンドの組み合わせごとに返り値を指定できます。Node.js標準のテストランナーで実行し、2件とも成功したコードです。実行前にnpm install -D aws-sdk-client-mock @smithy/util-streamで開発依存を追加し、mock.test.mjsとして保存して`node --test mock.test.mjs`で実行します。

```
import { test } from "node:test";
import assert from "node:assert/strict";
import { Readable } from "node:stream";
import { mockClient } from "aws-sdk-client-mock";
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { sdkStreamMixin } from "@smithy/util-stream";

async function loadConfig(s3, bucket) {
  const res = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: "config.json" }));
  return JSON.parse(await res.Body.transformToString());
}

const s3Mock = mockClient(S3Client);

test("S3のGetObjectをモックして設定を読む", async () => {
  s3Mock.reset();
  const body = sdkStreamMixin(Readable.from([Buffer.from('{"feature":true}')]));
  s3Mock.on(GetObjectCommand, { Bucket: "cfg", Key: "config.json" }).resolves({ Body: body });

  const conf = await loadConfig(new S3Client({ region: "ap-northeast-1" }), "cfg");
  assert.deepEqual(conf, { feature: true });
  assert.equal(s3Mock.commandCalls(GetObjectCommand).length, 1);
});

test("NoSuchKeyを投げさせる", async () => {
  s3Mock.reset();
  const err = Object.assign(new Error("not found"), { name: "NoSuchKey" });
  s3Mock.on(GetObjectCommand).rejects(err);
  await assert.rejects(loadConfig(new S3Client({ region: "ap-northeast-1" }), "cfg"), { name: "NoSuchKey" });
});
```

GetObject の Body は transformToString() を持つストリームである必要があるため、sdkStreamMixin で包みます。この @smithy/util-stream はSDKの依存として node\_modules の奥に入っているだけで、直接importすると ERR\_MODULE\_NOT\_FOUND になりました。npm install -D @smithy/util-stream で明示的に入れてください。mockClient はクライアントのプロトタイプを差し替えるので、テスト対象の関数の中で new S3Client() していてもモックが効きます。モックとスタブの使い分けは[モックとは？スタブとの違い・テストダブル5分類と単体テストでの使い分け](/tech/details/13505/)を参照してください。

## v3を採用する条件と、SDKを直接使わずに済ませる場面の判断

AWSのAPIをJavaScriptから呼ぶなら、v2が終了した現在、選択肢は事実上v3だけです。判断が分かれるのは「v3をどこで、どの粒度で使うか」です。

| 状況               | 判断               |
| ---------------- | ---------------- |
| v2が残るNode.jsサーバー | codemodで段階移行する   |
| Lambdaの新規関数      | v3を依存に入れて版を固定    |
| ブラウザからのアップロード    | 署名付きURLでSDKを配らない |
| S3互換ストレージ併用      | チェックサム設定を明示する    |
| 構築を繰り返す定型作業      | IaCやCLIに寄せる      |

v3を採用するのは、アプリケーションの処理の中でAWSのリソースを読み書きする場面です。S3のオブジェクト取得、DynamoDBの読み書き、SQSへの送信などが該当します。一方、バケットやテーブルの作成のようにインフラを組み立てる作業をSDKのスクリプトで書くのは勧めません。再実行したときの差分管理や削除の扱いを自前で書くことになるため、TerraformやAWS CDKなどのIaC、または[AWS CLI](/tech/details/17403/)に任せたほうが保守しやすくなります。

移行の優先度を決める軸は、Node.jsの版とブラウザ配布の有無の2つです。Node.js 20のままSDKだけ新しくしているCIは2027年1月のサポート終了が先に来るため、Node.jsの更新と同時に進めます。ブラウザへ aws-sdk 全体を配っているコードは、バンドルサイズの削減効果が大きいので最初に着手する対象です。v2のまま動いているバッチは、新しいAWSの機能を使わない限り急に止まるわけではありませんが、セキュリティ修正が出ない状態にあることを前提に計画へ入れます。

一創では、AWSの環境設計からSDKを使ったアプリケーション実装、v2からの移行までを受託で支援しています。既存システムの移行範囲の見積もりや、Lambda・S3・DynamoDBを組み合わせた構成の設計から相談したい場合は、[インフラ構築（AWS・Google Cloud・Azure）](https://www.issoh.co.jp/service/system/aws/)のページをご覧ください。

## よくある質問

### AWS SDK for JavaScript v2のサポートはいつ終了しましたか？

2025年9月8日です。2024年9月8日からの1年間はメンテナンスモードで、重大な不具合とセキュリティの修正だけが提供されていました。終了後も既存のコードは動きますが、新しいAWSサービスや機能には対応しないため、v3へ移行します。

### v3の最新バージョンはどう確認しますか？

npm view @aws-sdk/client-s3 version で確認できます。2026年10月14日時点の最新は 3.1151.0 です（本文の検証は2026年9月15日時点の 3.1132.0 で実施）。v3は頻繁に版が上がり、npmの公開履歴を数えると、2026年9月（UTC）だけで21の版が公開されていました。全クライアントの版番号が揃えて上がるので、複数のクライアントを使う場合は同じ版に揃えます。

### TypeScriptで使う場合、型定義パッケージを別に入れる必要はありますか？

必要ありません。@aws-sdk/client-s3 などの各クライアントは package.json の types に型定義（dist-types/index.d.ts）を指定しており、インストールしただけで型が付きます。@aws-sdk/types はクライアントの依存として入る共通の型のパッケージで、ミドルウェアなどを自作するときに直接importする程度です。

### v2とv3を同じプロジェクトで併用できますか？

できます。v2は aws-sdk、v3は @aws-sdk/ で始まる別名のパッケージなので衝突しません。モジュール単位で段階的に置き換え、最後に aws-sdk を依存から外します。ただし、v2の AWS.config で設定したリージョンや認証情報はv3のクライアントには反映されないため、移行したクライアントには設定を個別に渡します。

### ブラウザから直接S3にアクセスするには何が必要ですか？

一時的な認証情報とバケットのCORS設定の2つです。認証情報は @aws-sdk/credential-providers の fromCognitoIdentityPool でCognitoのIDプールから取得し、IAMロールで操作できるバケットとプレフィックスを絞ります。CORS設定がないと、ブラウザがプリフライトの段階でリクエストを止めるため、通信できません。アップロードだけならSDKをブラウザに配らず、署名付きURLを使う方法もあります。

## 関連記事

- [AWS S3の使い方｜CLIでのバケット作成から権限設計・署名付きURLまでの実装手順](/tech/details/17489/)
- [DynamoDBとは：ローカル起動からキー設計・容量モード選定までの実装手順](/tech/details/17453/)
- [AWS Lambdaとは？仕組み・料金体系とコールドスタート対策・採用判断を実装者目線で解説](/tech/details/15407/)
- [Node.jsとは？仕組み・版の選び方・標準機能で動かす手順を実装目線で解説](/tech/details/17433/)
- [AWS CLIの導入と運用｜v2の設定・SSO認証・v1サポート終了への移行手順](/tech/details/17403/)
- [TypeScriptとは？型・型推論・型注釈の書き分けとJavaScriptとの違い](/tech/details/2815/)

---

出典: [AWS SDK for JavaScript v3とは？使い方・v2からの移行手順とS3・DynamoDB実装例](<https://www.issoh.co.jp/tech/details/6814/>)（株式会社一創）
