skip to Main Content

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 Stringn

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?

2

Answers


  1. 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.

    Login or Signup to reply.
  2. Interestingly the DynamoDB API expects numerical fields of items as strings. For example:

    "N": "123.45"
    

    The doc says;

    Numbers are sent across the network to DynamoDB as strings, to maximize compatibility across languages and libraries. However, DynamoDB treats them as number type attributes for mathematical operations.

    Have you tried sending your input with the numerical parameters as strings as shown below? (See providerId and receiverId)

    {
       "id":"123",
       "content":"test feedback",
       "createdAt":"2022-12-02T08:45:26.261-05:00",
       "providerId":"457",
       "receiverId":"789"
    }
    

    You can convert these IDs into string when you’re populating your input Item:

      providerId: {
        N: String(commentary.getProviderId())
      },
      receiverId: {
        N: String(commentary.getReceiverId())
      }
    

    You could also use .toString() but then you’d get errors if the field is not set (null or undefined).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search