16

I am trying to get a basic database update operation to work in nodejs using the new AWS SDK V3 for NodeJS.

The data object that I am trying to update looks like this:

{
  auth: { BOOL: false },
  username: { S: 'siegbert' },
  secondsLeft: { N: 49985 },
  userid: { S: '123456' }
}

In the same file I have already succesfully done a GetItemCommand using the SDK V3.

Unfortunately I keep getting a really weird error when using the AWS SDK v3, when using the SDK v2, the exact same params seem to work. I tried looking into the docs but the update operation is not really well documented yet.

var params = {
    TableName: "tableXYZ",
    Key: {
        userid: user.userid.S,
    },
    UpdateExpression: "SET secondsLeft = :newsecondsLeft", 
    ExpressionAttributeValues: { 
        ":newsecondsLeft": user.secondsLeft.N,
    },
    ReturnValues: "UPDATED_NEW"
};


try {
        const data = await dbclient.send(new UpdateItemCommand(params));
        console.log("data:" + JSON.stringify(data));
        return true;
    } catch (error) {
        console.error(error);
        return false;
    }

This basically throws

TypeError: Cannot read property '0' of undefined
    at Object.AttributeValue.visit (XX\node_modules\@aws-sdk\client-dynamodb\dist\cjs\models\models_0.js:1101:40)
    at XX\node_modules\@aws-sdk\client-dynamodb\dist\cjs\protocols\Aws_json1_0.js:5074:20
    at Array.reduce (<anonymous>)
    at serializeAws_json1_0ExpressionAttributeValueMap (XX\node_modules\@aws-sdk\client-dynamodb\dist\cjs\protocols\Aws_json1_0.js:5068:34)
    at serializeAws_json1_0UpdateItemInput (XX\node_modules\@aws-sdk\client-dynamodb\dist\cjs\protocols\Aws_json1_0.js:6067:40)
    at Object.serializeAws_json1_0UpdateItemCommand (XX\node_modules\@aws-sdk\client-dynamodb\dist\cjs\protocols\Aws_json1_0.js:474:27)
    at serialize (XX\node_modules\@aws-sdk\client-dynamodb\dist\cjs\commands\UpdateItemCommand.js:42:30)
    at XX\node_modules\@aws-sdk\middleware-serde\dist\cjs\serializerMiddleware.js:5:27
    at XX\node_modules\@aws-sdk\middleware-logger\dist\cjs\loggerMiddleware.js:6:28

When using the exact same params but with the SDK v2, it works:

var docClient = new AWS.DynamoDB.DocumentClient();

docClient.update(params, function (err, data) {
    if (err) {
        console.error("Unable to update item. Error JSON:", JSON.stringify(err, null, 2));
    } else {
        console.log("UpdateItem succeeded:", JSON.stringify(data, null, 2));
    }
});

Any help on how to use the SDK V3 for the update would be appreciated!

Eesa
  • 2,762
  • 2
  • 32
  • 51
Dusdoron
  • 163
  • 1
  • 1
  • 5
  • What is `dbclient`? Is it the v3 equivalent of the v2 DocumentClient, which is what your working version uses? – jarmod Mar 11 '21 at 23:55

3 Answers3

26

Couple of corrections:

  • when passing values we need to pass the object with type. so, instead of user.userid.S pass entire user.userid. Since its not able to determine the type, it is assuming as an array and trying to get the first element of the array and resulting in that error.
  • Numeric values, should simply be passed as String value of type 'N',like secondsLeft: { N: "49985" }

Here is the updated code.

const { DynamoDB, UpdateItemCommand } = require("@aws-sdk/client-dynamodb");
const dbclient = new DynamoDB({ region: "us-east-1" });
const user = {
  auth: { BOOL: false },
  username: { S: "siegbert" },
  secondsLeft: { N: "49985" },
  userid: { S: "123456" },
};
var params = {
  TableName: "tableXYZ",
  Key: {
    id: user.userid,
  },
  UpdateExpression: "SET secondsLeft = :newsecondsLeft",
  ExpressionAttributeValues: {
    ":newsecondsLeft": user.secondsLeft,
  },
  ReturnValues: "UPDATED_NEW",
};

dbclient
  .send(new UpdateItemCommand(params))
  .then((result) => {
    console.log("data:" + result);
  })
  .catch((err) => {
    console.log("err", err);
  });
Balu Vyamajala
  • 9,287
  • 1
  • 20
  • 42
  • Thanks! I will mark this as correct solution. I changed the format and the final hint was the "pass number as String value of type N", my problem was that I previously did a substraction on the secondsLeft value which let it get passed as a numeric value ... A quick secondsLeft.toString() solved the issue! – Dusdoron Mar 12 '21 at 15:43
7

You can use the @aws-sdk/lib-dynamodb package, which is a closer replacement to the v2 style of aws-sdk. From the documentation:

The document client simplifies working with items in Amazon DynamoDB by abstracting away the notion of attribute values. This abstraction annotates native JavaScript types supplied as input parameters, as well as converts annotated response data to native JavaScript types.

This is perhaps a cleaner alternative (and easier to upgrade to) to using @aws-sdk/client-dynamodb directly, although you will still need to instantiate that client as an input to using @aws-sdk/lib-dynamodb.

Here is their example code:

import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; // ES6 import
// const { DynamoDBClient } = require("@aws-sdk/client-dynamodb"); // CommonJS import
import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb"; // ES6 import
// const { DynamoDBDocumentClient, PutCommand } = require("@aws-sdk/lib-dynamodb"); // CommonJS import

const client = new DynamoDBClient({});
const ddbDocClient = DynamoDBDocumentClient.from(client);

await ddbDocClient.put({
  TableName,
  Item: {
    id: "2",
    content: "content from DynamoDBDocument",
  },
});
Brent L
  • 761
  • 7
  • 5
  • 1
    you literally saved my day! I was importing Transact*Commands from aws-sdk/client-dynamodb and passing pure JS objects until I saw your reply. aws-sdk/lib-dynamodb solved my issue – quartaela Aug 09 '22 at 21:59
7

Using the DynamoDBDocumentClient from the lib-dynamodb module makes it easier:

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

const test = async () => {

    const client = new DynamoDBClient({region: 'eu-west-1'})

    const params = {
        TableName: "tableXYZ",
        Item: 
            {
            userid: 'userId',
                ...user
        }
    }

    const docClient = DynamoDBDocumentClient.from(client);

    try {
        const response = await docClient.send(new PutCommand(params))
        console.log(response)
    } catch (e) {
        console.error(e)
    }
}

test()

(this can be run as a stand-alone node script)

Ciryon
  • 2,637
  • 3
  • 31
  • 33
  • 2
    Thx. I had imported the QueryCommand from @aws-sdk/client-dynamodb instead of importing from @aws-sdk/lib-dynamodb – Gilbert Aug 04 '22 at 04:14
  • Thanks! Doing the simple web app tutorial but selected Node.js 18.x for the lambda which messed me up quite a bit although other things were quite outdated as well. Your example was the only one that worked for me. https://aws.amazon.com/getting-started/hands-on/build-web-app-s3-lambda-api-gateway-dynamodb – upnorth Jan 25 '23 at 13:00