DynamoDB.js

Utility functions for DynamoDB

import AWS from 'aws-sdk'; import { getScanParams, getUpdateParams } from './dynamo-util.js'; const docClient = new AWS.DynamoDB.DocumentClient({ region: process.env.AWS_REGION, }); const defaultTable = process.env.TABLE_NAME; // *=========== GET =============* export async function getItem(id, tableName) { return await docClient .get({ TableName: tableName || defaultTable, Key: { id } }) .promise(); } // *=========== ADD =============* export function addItem(Item, tableName) { docClient.put({ TableName: tableName || defaultTable, Item }, function (err, data) { if (err) console.error('DynamoDB ERROR:', err); else console.log('Successfully added item ' + Item.id); }); } // *=========== UPDATE =============* const updateParams = (id, obj, tableName) => { if (!id || !Object.keys(obj).length) { return undefined; } let updateExpression = 'set'; let ExpressionAttributeNames = {}; let ExpressionAttributeValues = {}; for (const property in obj) { updateExpression += ` #${property} = :${property} ,`; ExpressionAttributeNames['#' + property] = property; ExpressionAttributeValues[':' + property] = obj[property]; } updateExpression = updateExpression.slice(0, -1); return { TableName: tableName || defaultTable, Key: { id }, UpdateExpression: updateExpression, ExpressionAttributeNames: ExpressionAttributeNames, ExpressionAttributeValues: ExpressionAttributeValues, }; }; export const updateItem = (id, obj, tableName) => { const params = updateParams(id, obj, tableName); docClient.update(params, function (err, data) { if (err) console.error(err); else console.log(`Updated ${id}`); }); }; export const updateItemAsync = async (id, obj, tableName) => { const params = updateParams(id, obj, tableName); return await docClient.update(params).promise(); }; // *=========== SCAN =============* export async function getAllItemsAsync(params) { let scanResults = []; let res; do { res = await docClient.scan(params).promise(); if (res.Items) res.Items.forEach((item) => scanResults.push(item)); params.ExclusiveStartKey = res.LastEvaluatedKey; } while (typeof res.LastEvaluatedKey != 'undefined'); return scanResults; } // This readability is embarrassing function getScanParams(start, end, otherFilter, tableName) { let FilterExpression = ''; let values = {}; if (!!start) { FilterExpression += 'created > :start'; values = { ':start': start }; } if (!!start && !!end) FilterExpression += ' and '; if (!!end) { FilterExpression += 'created < :end'; values = { ...values, ':end': end }; } FilterExpression += !!!otherFilter ? '' : start || end ? ' and ' + otherFilter : otherFilter; const expressions = start || end || otherFilter ? { FilterExpression, ExpressionAttributeValues: start || end ? values : undefined, } : {}; return { TableName: tableName || defaultTable, ...expressions }; } export async function scanTableAsync(start, end, otherFilter, tableName) { const params = getScanParams(start, end, otherFilter, tableName); return await getAllItems(params); } export function scanTable(FilterExpression, tableName, callback) { var params = { TableName: tableName || defaultTable, FilterExpression }; docClient.scan(params, function (err, data) { if (err) console.error('DynamoDB ERROR:', err); else { callback(data.Items); } }); } // *=========== REMOVE FIELDS =============* export function removeFields(id, fieldNamesArray, tableName, callback) { var params = { TableName: tableName || defaultTable, Key: { id }, UpdateExpression: `REMOVE ${fieldNamesArray.join(', ')}`, ReturnValues: 'UPDATED_NEW', }; docClient.update(params, function (err, data) { if (err) console.error('DynamoDB ERROR:', err); else callback(data.Attributes); }); }
js

dynamodb-util.js

export const getUpdateParams = (id, obj, tableName) => { if (!id || !Object.keys(obj).length) { return undefined; } let updateExpression = 'set'; let ExpressionAttributeNames = {}; let ExpressionAttributeValues = {}; for (const property in obj) { updateExpression += ` #${property} = :${property} ,`; ExpressionAttributeNames['#' + property] = property; ExpressionAttributeValues[':' + property] = obj[property]; } updateExpression = updateExpression.slice(0, -1); return { TableName: tableName || defaultTable, Key: { id }, UpdateExpression: updateExpression, ExpressionAttributeNames: ExpressionAttributeNames, ExpressionAttributeValues: ExpressionAttributeValues, }; }; export const getScanParams = (start, end, otherFilter, tableName) => { let FilterExpression = ''; let values = {}; if (!!start) { FilterExpression += 'created > :start'; values = { ':start': start }; } if (!!start && !!end) FilterExpression += ' and '; if (!!end) { FilterExpression += 'created < :end'; values = { ...values, ':end': end }; } FilterExpression += !!!otherFilter ? '' : start || end ? ' and ' + otherFilter : otherFilter; const expressions = start || end || otherFilter ? { FilterExpression, ExpressionAttributeValues: start || end ? values : undefined, } : {}; return { TableName: tableName || defaultTable, ...expressions }; };
js

Usage

Here's an example with scan and update. This will update the last week of data where fieldToUpdate is null:

const aWeekAgo = new Date(Date.now().valueOf() - 7 * 24 * 60 * 60 * 1000).toISOString(); const items = await scanTableAsync( eightDaysAgo, undefined, `attribute_not_exists(fieldToUpdate)` ); for (let i = 0; i < items.length; i++) { const item = items[i]; console.log('UPDATING', item.id); updateItem(item.id, { fieldToUpdate: 'new value' }); }
js

Note: The code above should only be used for one-off operations, like infrequent cron-jobs or if there was a gap in your data for some reason. Scan operations are expensive and should not be used regularly, instead use indexes/keys for querying a NoSQL database.