The UpdateCommand
in the AWS DynamoDB documentation is not very well documented and hard to use.
I wanted to use the update command to just take a Javascript object and insert all of the keys from that onto the object matching the primary key.
Looking at the InsertCommand
I thought it would be simple like this:
async updateItem(tableName: string, primaryKeyName: string, id: string, item: { [key: string]: any }) {
const input: UpdateCommandInput = {
Key: {
[primaryKeyName]: id,
},
AttributeUpdates: item,
TableName: tableName,
};
const command = new UpdateCommand(input);
return await this.client.send(command);
}
But this seems to be failing.
Now when I was looking around the documentation was hard to come by but I managed to piece it together over several different sources.
I have created a function that will take an object and then just overwrite with all those properties on the object referenced with primary key. I hope this can be of help to others having a hard time figuring out how UpdateCommand
works.
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import {
UpdateCommandInput,
UpdateCommand,
DynamoDBDocumentClient,
} from '@aws-sdk/lib-dynamodb';
export class DynamoDBService {
private client: DynamoDBDocumentClient;
constructor() {
const client = new DynamoDBClient({
region: config.get(<<your_region>>),
});
const marshallOptions = {
// Whether to automatically convert empty strings, blobs, and sets to `null`.
convertEmptyValues: false, // false, by default.
// Whether to remove undefined values while marshalling.
removeUndefinedValues: false, // false, by default.
// Whether to convert typeof object to map attribute.
convertClassInstanceToMap: true, // false, by default.
};
this.client = DynamoDBDocumentClient.from(client, { marshallOptions });
}
/**
* Takes a javascript object and transforms it into update expressions on the dynamodb object.
*
* It translates all of the actions to SET which will overwrite any attribute that is already there.
* It works best with simple types but can also serialise arrays and objects.
*
* https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/dynamodb-example-dynamodb-utilities.html
*
* @param tableName Name of the table to update on
* @param primaryKeyName The primary key name to update on
* @param id The primary key value
* @param item The item to update
*/
async upsertProperties(tableName: string, primaryKeyName: string, id: string, item: { [key: string]: any }) {
const updatedItem = this.removePrimaryKey(primaryKeyName, item);
const { updateExpression, expressionAttribute, expressionAttributeNames } =
this.createUpdateExpressions(updatedItem);
const input: UpdateCommandInput = {
Key: {
[primaryKeyName]: id,
},
UpdateExpression: `SET ${updateExpression.join(', ')}`,
ExpressionAttributeValues: expressionAttribute,
ExpressionAttributeNames: expressionAttributeNames,
TableName: tableName,
};
const command = new UpdateCommand(input);
return await this.client.send(command);
}
/**
* We alias properties to be sure we can insert reserved names (status fx).
*
* It is a bit complicated:
* updateExpression: The actual update state, example: SET #alias = :placeholder
* expressionAttribute: The value to insert in placeholder example: :placeholder = value
* expressionAttributeNames: Are the aliases properties to avoid clashe: #alias = key
*
* So in the end it ties together and both the inserted value and alias are fixed.
*/
private createUpdateExpressions(item: { [key: string]: any }) {
const updateExpression: string[] = [];
const expressionAttribute: { [key: string]: any } = {};
const expressionAttributeNames: { [key: string]: any } = {};
Object.keys(item).map((key) => {
const placeholder = `:p${key}`;
const alias = `#a${key}`;
updateExpression.push(`${alias} = ${placeholder}`);
expressionAttribute[placeholder] = item[key];
expressionAttributeNames[alias] = key;
});
return { updateExpression, expressionAttribute, expressionAttributeNames };
}
/**
* Remove primary key from the object or else we cannot make an
* update statement because the primary key would be overwritten
* which violates the insert.
*
* Copy to new object to ensure we don't manipulate the reference.
*/
private removePrimaryKey(primaryKeyName: string, item: { [key: string]: any }) {
const itemWithoutPrimaryKey = { ...item };
delete itemWithoutPrimaryKey[primaryKeyName];
return itemWithoutPrimaryKey;
}
}