Search code examples
amazon-web-servicesamazon-dynamodbaws-sdk-js

DynamoDB JavaScript PutItemCommand is neither failing nor working


Please note: although this question mentions AWS SAM, it is 100% a DynamoDB JavaScript SDK question at heart and can be answered by anyone with experience writing JavaScript Lambdas (or any client-side apps) against DynamoDB using the AWS DynamoDB client/SDK.


So I used AWS SAM to provision a new DynamoDB table with the following attributes:

FeedbackDynamoDB:
  Type: AWS::DynamoDB::Table
  Properties:
    TableName: commentary
    AttributeDefinitions:
      - AttributeName: id
        AttributeType: S
    KeySchema:
      - AttributeName: id
        KeyType: HASH
    ProvisionedThroughput:
      ReadCapacityUnits: 5
      WriteCapacityUnits: 5
    StreamSpecification:
      StreamViewType: NEW_IMAGE

This configuration successfully creates a DynamoDB table called commentary. However, when I view this table in the DynamoDB web console, I noticed a few things:

  • it has a partition key of id (type S)
  • it has no sort key
  • it has no (0) indexes
  • it has a read/write capacity mode of "5"

I'm not sure if this raises any red flags with anyone but I figured I would include those details, in case I've configured anything incorrectly.

Now then, I have a JavaScript (TypeScript) Lambda that instantiates a DynamoDB client (using the JavaScript SDK) and attempts to add a record/item to this table:

// this code is in a file named app.ts:
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { User, allUsers } from './users';
import { Commentary } from './commentary';
import { PutItemCommand } from "@aws-sdk/client-dynamodb";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";

export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
    try {

        const ddbClient = new DynamoDBClient({ region: "us-east-1" });

        let status: number = 200;
        let responseBody: string = "\"message\": \"hello world\"";

        const { id, content, createdAt, providerId, receiverId } = JSON.parse(event.body);
        const commentary = new Commentary(id, content, createdAt, providerId, receiverId);
        console.log("deserialized this into commentary");
        console.log("and the deserialized commentary has content of: " + commentary.getContent());
        await provideCommentary(ddbClient, commentary);
        responseBody = "\"message\": \"received commentary -- check dynamoDb!\"";

        return {
            statusCode: status,
            body: responseBody
        };

    } catch (err) {
        console.log(err);
        return {
            statusCode: 500,
            body: JSON.stringify({
                message: err.stack,
            }),
        };
    }
};

const provideCommentary = async (ddbClient: DynamoDBClient, commentary: Commentary) => {

  const params = {
    TableName: "commentary",

    Item: {
      id: {
        S: commentary.getId()
      },
      content: {
        S: commentary.getContent()
      },
      createdAt: {
        S: commentary.getCreatedAt()
      },
      providerId: {
        N: commentary.getProviderId()
      },
      receiverId: {
        N: commentary.getReceiverId()
      }
    }

  };

  console.log("about to try to insert commentary into dynamo...");

  try {
    console.log("wait for it...")
    const rc = await ddbClient.send(new PutItemCommand(params));
    console.log("DDB response:", rc);
  } catch (err) {
    console.log("hmmm something awry. something....in the mist");
    console.log("Error", err.stack);
    throw err;
  }
};

Where commentary.ts is:

class Commentary {
  private id: string;
  private content: string;
  private createdAt: Date;
  private providerId: number;
  private receiverId: number;

  constructor(id: string, content: string, createdAt: Date, providerId: number, receiverId: number) {
    this.id = id;
    this.content = content;
    this.createdAt = createdAt;
    this.providerId = providerId;
    this.receiverId = receiverId;
  }

  public getId(): string {
    return this.id;
  }

  public getContent(): string {
    return this.content;
  }

  public getCreatedAt(): Date {
    return this.createdAt;
  }

  public getProviderId(): number {
    return this.providerId;
  }

  public getReceiverId(): number {
    return this.receiverId;
  }

}

export { Commentary };

When I update the Lambda with this handler code, and hit the Lambda with the following curl (the Lambda is invoked by an API Gateway URL that I can hit via curl/http):

curl -i --request POST 'https://<my-api-gateway>.execute-api.us-east-1.amazonaws.com/Stage/feedback' \
--header 'Content-Type: application/json' -d '{"id":"123","content":"test feedback","createdAt":"2022-12-02T08:45:26.261-05:00","providerId":457,"receiverId":789}'

I get the following HTTP 500 response:

{"message":"SerializationException: NUMBER_VALUE cannot be converted to String\n

Am I passing it a bad request body (in the curl) or do I need to tweak something in app.ts and/or commentary.ts?


Solution

  • Try using a promise to see the outcome:

    client.send(command).then(
      (data) => {
        // process data.
      },
      (error) => {
        // error handling.
      }
    );
    

    Everything seems alright with your table setup, I believe it's Lambda async issue with the JS sdk. I'm guessing Lambda is not waiting on your code and exiting early. Can you include your full lambda code.