expressaws-lambdaamazon-dynamodbdynamodb-queriesamazon-dynamodb-index

Querying Items with DynamoDBClient using an Global Secondary Index (GSI) with a partition key (deviceId) and a sort key (timestamp)


So I have a Lambda function acting as a Express.js server and an API. I am hitting all of the endpoint correctly and data is being returned the way I want. The sole problem I am left with is a function that is supposed to return a list of items in the following format:

[
 {
 "id": "d8aaa50e-5b4d-4a6f-84f5-e508e594f84d",
 "deviceId": "dkajshdaksdhj",
 "humidity": "45",
 "light": "35",
 "ph": "55",
 "temp": "65",
 "timestamp": 1696600485,
 "waterTemp": "75"
 },
 {...}
]

However when performing a QueryCommand on my table like so:

      const data = await dynamoDb.send(
        new QueryCommand({
          TableName: process.env.DYNAMODB_TABLE_NAME_READINGS,
          IndexName: 'deviceId-timestamp-index', // Use the name of your Global Secondary Index
          KeyConditionExpression: '#deviceId = :deviceId AND #t BETWEEN #e AND #s',
          ExpressionAttributeNames: {
            '#deviceId': 'deviceId',
            '#t': 'timestamp',
            '#e': 'end',
            '#s': 'start',
          },
          ExpressionAttributeValues: marshall(
            {
              ':deviceId': deviceId,
              ':s': start,
              ':e': end,
            },
            {
              removeUndefinedValues: true,
            },
          ),
          ScanIndexForward: false,
        }),
      );

I am getting hit with the following error:

{
    "status": "error",
    "statusCode": "500",
    "stack": "Error: ValidationException: One or more parameter values were invalid: Condition parameter type does not match schema type\n    at Object.getHistoricalReadingsForDeviceId (file:///var/task/code/services/deviceService.js:240:11)\n    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n    at async getHistoricalReadings (file:///var/task/code/controllers/deviceController.js:75:32)"
}

Now I have checked both with ChatGPT, AWS Documentation and StackOverflow but after a day of trying out things I am now without options.

In the AWS Console the following Query works properly and it is returning items: enter image description here

However when I try both on my deployed website and in Postman the result is the same (this error):

enter image description here

Link to repository in Github and exact file and line of the function is located: click

My speculation is that it is complaining either because I do not have start or end as attributes in my table or because I am trying to marshall multiple things at once? Link to marshall/unmarshall docs. It is basically an utility function that is helping with converting from/to DynamoDB attribute types.


Solution

  • ExpressionAttributeNames are used for attribute names, such as deviceId and timestamp.

    ExpressionAttributeValues are used for values, such as start and end.

    Your code has mixed these up. Below should work.

    const data = await dynamoDb.send(
            new QueryCommand({
              TableName: process.env.DYNAMODB_TABLE_NAME_READINGS,
              IndexName: 'deviceId-timestamp-index', // Use the name of your Global Secondary Index
              KeyConditionExpression: '#deviceId = :deviceId AND #t BETWEEN :s AND :e',
              ExpressionAttributeNames: {
                '#deviceId': 'deviceId',
                '#t': 'timestamp'
              },
              ExpressionAttributeValues: marshall(
                {
                  ':deviceId': deviceId,
                  ':s': start,
                  ':e': end,
                }
              ),
              ScanIndexForward: false,
            }),
          );
    

    As it's clear you'd rather work with native JSON, I believe you would see greater benefit using the DocumentClient, you can read more about that here

    No marshalling required, it also means your results won't need to be unmarshalled:

    const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
    const { DynamoDBDocumentClient, QueryCommand } = require("@aws-sdk/lib-dynamodb");
    
    const client = new DynamoDBClient({});
    const docClient = DynamoDBDocumentClient.from(client);
    
    const data = await docClient.send(
            new QueryCommand({
              TableName: process.env.DYNAMODB_TABLE_NAME_READINGS,
              IndexName: 'deviceId-timestamp-index', // Use the name of your Global Secondary Index
              KeyConditionExpression: '#deviceId = :deviceId AND #t BETWEEN :s AND :e',
              ExpressionAttributeNames: {
                '#deviceId': 'deviceId',
                '#t': 'timestamp'
              },
              ExpressionAttributeValues: {
                  ':deviceId': deviceId,
                  ':s': start,
                  ':e': end,
                },
              ScanIndexForward: false,
            }),
          );