Node.js (AWS SDK v3) での DynamoDB PutItem

PutItem はアイテム 1 件をまるごと書き込み、同じプライマリキーの既存アイテムがあれば置き換えます(UpdateItem との違いはアイテム単位のアクションが扱います)。v3 のクライアントは DynamoDB JSON を直接送るので、Item には素の JavaScript ではなく { S: … } / { N: … } の値が入ります。

コード

import {DynamoDBClient, PutItemCommand} from '@aws-sdk/client-dynamodb';

const client = new DynamoDBClient({region: 'us-east-1'});

const command = new PutItemCommand({
  TableName: 'Music',
  Item: {
    Artist: {S: 'Arturo Sandoval'},
    SongTitle: {S: 'Cubano Chant'},
    AlbumTitle: {S: 'Danzon'},
    Year: {N: '1994'},
    Awards: {N: '0'}
  },
  ConditionExpression: 'attribute_not_exists(#cond0) AND attribute_not_exists(#cond1)',
  ExpressionAttributeNames: {
    '#cond0': 'Artist',
    '#cond1': 'SongTitle'
  }
});

try {
  await client.send(command);
  console.log('Song written');
} catch (err) {
  if (err.name === 'ConditionalCheckFailedException') {
    console.log('A song with that key already exists — not overwritten');
  } else {
    throw err;
  }
}

解説

チェックすべきは err.name であり、エラーに載っているのはそれだけではありません。上の条件失敗を捕捉してオブジェクトを表示すると、こうなりました。

err.name                     ConditionalCheckFailedException
err instanceof Error         true
err.message                  The conditional request failed
err.$metadata.httpStatusCode 400

v3 のエラーはすべて、ステータスコード、リクエスト ID、試行回数を持つ $metadata を伴います。ログ 1 行に欲しいのはまさにそれです。err.name はモジュール化されたパッケージをまたいで安定しています。instanceof ConditionalCheckFailedException も動きますが、クラスを値として import することになるので、バンドラーがそれを残します。

エラーは、書き込みを阻んだアイテムを渡してくれます。コマンドに ReturnValuesOnConditionCheckFailure: 'ALL_OLD' を足すと err.Item が中身入りで届きます。上の実行では 5 つの属性が入り、Year{"N":"1994"} でした。作成専用のハンドラーの多くは、失敗の後に GetItem を打ってすでに何があったのかを調べます。その往復は避けられます。(ReturnValues: 'ALL_OLD' は成功パス側の従兄弟です。残りは ReturnValues が扱います。)

marshall() は思っている以上に多くの入力を拒みます。このページの型付き ItemDynamoDBDocumentClient と素のオブジェクトに置き換えるのはよくある次の一手ですが、@aws-sdk/util-dynamodb はデフォルトで厳格です。実際に起きた 3 つのスロー、そのままの逐語です。

{Genre: undefined}   Pass options.removeUndefinedValues=true to remove undefined values from map/array/set.
{tags: new Set()}    Pass a non-empty set, or options.convertEmptyValues=true.
{n: 9007199254740993} Number 9007199254740992 is greater than Number.MAX_SAFE_INTEGER. Use NumberValue from @aws-sdk/lib-dynamodb.

本番に届くのは 1 つ目です。存在しないのではなく undefined であるオプショナルなフィールドがマーシャル時にスローするので、DynamoDBDocumentClient.from(client, {marshallOptions})removeUndefinedValues: true を入れるのが定番の対処です。

3 行目をもう一度読んでください。リテラルは 9007199254740993 でしたが、メッセージは 9007199254740992 を引用しています。SDK が値を見る前に JavaScript がすでに丸めていたので、SDK は受け取ったものを報告しているのです。DynamoDB が N を文字列として運ぶ理由は、まさにこれです。DynamoDB は 38 桁の精度を保持し、JS の number が保持するのは 15〜17 桁です。実体が識別子であるものは S に、実体が小数であるものは NumberValue か自分で整形した文字列に置きましょう。

ConditionExpression は、否と言うときにも書き込みキャパシティを使います。AWS 曰く: "if the expression evaluates to false, DynamoDB still consumes write capacity units from the table"(2026-07-28 取得)。作成専用のリトライをタイトループで回すと、試行ごとに課金されます。目安として、約 15 KB のアイテムの put が成功したときは "CapacityUnits": 15 と報告されました。書き込みは、読み取りが使う 4 KB ではなく 1 KB 単位で切り上げられます。

エイリアスは飾りではありません#cond0/#cond1ExpressionAttributeNames を通じて Artist/SongTitle に解決されます。名前を直接書く書き方は、どれかが予約語と衝突するまでは動き、衝突した途端、触ってもいない属性のせいで式が失敗します。

ビジュアルに行う

上のマーシャリングのルールは、両方の形を並べて見るのが一番確かめやすいです。無料の DynamoDB JSON コンバーターは、素の JSON を型付きの { S: … } 形式に、またその逆に変換するので、送る前に marshall() が何を作るはずだったのかを確認できます。

自分のテーブルに対してアイテムを書いたり編集したりするには(属性ごとのフォーム、型ピッカー、結果を SDK v3 のコードとしてコピー)、DynoTable をダウンロードしてください。

関連ガイド

参考資料

2026-07-28 に Node v24.18.0、@aws-sdk/client-dynamodb 3.1095.0、@aws-sdk/util-dynamodb 3.996.7 で、ポート 9000 の DynamoDB Local(amazon/dynamodb-local)に対して再現しました。エラー文字列、オブジェクトの形、キャパシティの数値は捕捉した出力で、そのままの逐語です。

Console なしで DynamoDB を扱う

DynamoDB では実行できない本物の SQL(JOINs、GROUP BY、集計)を実行する高速な DynamoDB デスクトップクライアント。ビジュアル編集と、あなた自身の Bedrock キーで動く AI エージェントを備えています。

30日間無料トライアル、クレジットカード不要 — その後は期限のない Free プラン。