@AwsPilot's DynamoDB

Speak fluent DynamoDB, write code with fashion, I Promise() 😃

npm page

Installation

npm install @awspilot/dynamodb // check for new versions npm outdated // upgrade if necessary npm update @awspilot/dynamodb

Testing

tests will run in a in-memory implementation of DynamoDB server dynalite

npm test

If you want to test against your own DynamoDB database, add AWS credentials to your environment first

source test/credentials npm test

Init

var $credentials = { "accessKeyId": "XXXXXXXXXXXXXXXX", "secretAccessKey": "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ", "region": "eu-west-1" } var DynamoDB = require('@awspilot/dynamodb')($credentials) // Alternatively, use an existing instance of AWS.DynamoDB. var AWS = require('aws-sdk'); var $db = new AWS.DynamoDB(); var DynamoDB = require('@awspilot/dynamodb')($db);

Raw Calls to aws sdk

DynamoDB.client.listTables(function(err, data) { console.log(data.TableNames); }); console.log( DynamoDB.client )

Global error feed

// every call to Amazon DynamoDB that fail will // call this function before the operation's callback DynamoDB.on('error', function( operation, error, payload ) { // you could use this to log fails into LogWatch for // later analysis or SQS queue lazy processing })

Response

If a callback function is supplied, the response will be returned as callback(error, data)

If no callback function is supplied, Promise will be returned

DynamoDB .table() .method(parameters, function( err, response ) { })
DynamoDB .table() .method(parameters) .then( callback_if_success, callback_if_failed ) .catch( callback_if_failed )

Insert Item

does not replace existing items

DynamoDB .table('users') .insert({ email: 'test@test.com', password: 'qwert', created_at: new Date().getTime(), updated_at: null }, function(err,data) { console.log( err, data ) }) DynamoDB .table('messages') .insert({ to: 'test@test.com', date: new Date().getTime(), subject: 'Foo', message: 'Bar' }) .then(console.log, console.log) .catch(console.log) // insert item with nested attributes // carefull though as foo.bar domain actually exists :) DynamoDB .table('messages') .insert({ to: 'test@test.com', date: new Date().getTime(), boolean_true: true, boolean_false: false, key_null: null, string: "string", number: 1, buffer: new Buffer("test"), array_empty: [], array_strings: ['alfa','beta','gama'], // inserted as datatype L string_set: DynamoDB.SS(['sss','bbb','ccc']), // inserted as datatype SS array_numbers: [7,9,15], // inserted as datatype L number_set: DynamoDB.NS([111,222,333]), // inserted as datatype NS array_mixed: [ null, "string", 5, true, false, { key: "value"}, ["nested","array"], new Buffer("test") ], nested_object: { name: "Foo", email: "baz@foo.bar", nested_attribute: { boolean_value: true, null_key: null, some_string: "tadaa", lucky_number: 12 } } })

Insert on Duplicate Item Update

DynamoDB .table('users') .where('email').eq('test@test.com') .return(DynamoDB.UPDATED_OLD) .insert_or_update({ password: 'qwert', firstname: 'Smith', number: 5, page_views: DynamoDB.add(5), // increment by 5 list: [5,'a', {}, [] ], // nested attributes phones: DynamoDB.add([5,'a']), // push these elements at the end of the list (L) string_set: DynamoDB.add(DynamoDB.SS(['ddd','eee'])), // add to SS, number_set: DynamoDB.add(DynamoDB.NS([444,555])), // add to NS, unneeded_attribute: DynamoDB.del(), unneeded_ss_items: DynamoDB.del(DynamoDB.SS(['ccc','ddd'])), // remove elements from stringSet unneeded_ns_items: DynamoDB.del(DynamoDB.NS([111,444])), // remove elements from numberSet }, function( err, data ) { console.log( err, data ) })

Insert on Duplicate Item Replace

1 request to DynamoDB

DynamoDB .table('users') .return(DynamoDB.ALL_OLD) .insert_or_replace({ email: 'test@test.com', password: 'qwert', firstname: 'Smith' }, function( err, data ) { console.log( err, data ) })

Update Item

does not create the item if item does not exist

// update multiple attributes in a HASH table DynamoDB .table('users') .where('email').eq('test@test.com') .return(DynamoDB.ALL_OLD) .update({ password: 'qwert', firstname: 'Smith', page_views: DynamoDB.add(5), // increment by 5 list: [5,'a', {}, [] ], // nested attributes phones: DynamoDB.add([5,'a']), // push these elements at the end of the array unneeded_attribute: DynamoDB.del(), }, function( err, data ) { console.log( err, data ) }) // update 1 attribute in a HASH-RANGE table DynamoDB .table('messages') .where('to').eq('user1@test.com') .where('date').eq( 1375538399 ) .update({ seen: true }, function( err, data ) { console.log( err, data ) })

Replace Item

does not create the item if item does not exist

// completely replaces the item, new item will only contain specified attributes DynamoDB .table('users') .return(DynamoDB.UPDATED_OLD) .replace({ email: 'test@test.com', password: 'qwert', created_at: new Date().getTime() }, function(err,data) { console.log( err, data ) })

Increment Item's Attribute(s)

( does not create the item if item does not exist )

// increment attributes, if the key does not exist, it is not created DynamoDB .table('users') .where('email').eq('test@test.com') .update({ login_count: DynamoDB.add(), // increment by 1 add5: DynamoDB.add(5), // increment by 5 sub7: DynamoDB.add(-7) // decrement by 7 }, function( err, data ) { console.log( err, data ) }) // increment attributes, if the key does not exist, it is created DynamoDB .table('users') .where('email').eq('test@test.com') .insert_or_update({ login_count: DynamoDB.add(), // increment by 1 add5: DynamoDB.add(5), // increment by 5 sub7: DynamoDB.add(-7) // decrement by 7 }, function( err, data ) { console.log( err, data ) })

Delete Item's Attribute(s)

DynamoDB .table('messages') .where('to').eq('user1@test.com') .where('date').eq( 1375538399 ) .update({ seen: DynamoDB.del(), subject: DynamoDB.del() }, function(err) {})

Delete Item

delete() does not fail if the item does not exist

// delete an item from a HASH table DynamoDB .table('users') .where('email').eq( 'test@test.com' ) .delete(function( err, data ) { console.log( err, data ) }) // delete an item from a HASH-RANGE table DynamoDB .table('messages') .where('to').eq( 'user1@test.com' ) .where('date').eq( 1375538399 ) .return(DynamoDB.ALL_OLD) .delete(function( err, data ) { console.log( err, data ) })

Get Item

// getting an item with HASH key only DynamoDB .table('users') .where('email').eq('test@test.com') .get(function( err, data ) { console.log( err, data ) }) // getting an item from a HASH-RANGE table, with consistent read DynamoDB .table('messages') .where('to').eq('user1@test.com') .where('date').eq( 1375538399 ) .consistent_read() .get(function( err, data ) { console.log( err, data ) }) // specifying what attributes to return DynamoDB .table('users') .select('email','registered_at','object.attribute','string_set[0]','array[1]') .where('email').eq( 'test@test.com' ) .get(function( err, data ) { console.log( err, data ) })

Query

not possible on HASH only tables

// for hash key comparson operator is always eq() // for range key you can specify: le() , lt() , ge() , gt() , begins_with() , between(a,b) // base query, return 10 records with consistent read DynamoDB .table('statistics') .where('domain').eq('mydomain.com') .limit(10) .consistent_read() .query(function(err, data ) { console.log(err,data) }) // only return specified fields, in descending order DynamoDB .table('statistics') .select('unique_visitors','unique_pageviews','object.attribute','string_set[0]','array[1]') .where('domain').eq('mydomain.com') .where('day').ge('2013-11-01') .descending() .query(function( err, data ) { console.log( err, data ) })

Query an Index with order_by()

// suppose you have an index on messages called starredIndex // and you want to retrieve only the messages that are starred DynamoDB .table('messages') .where('to').eq('user1@test.com') .order_by('starredIndex') .descending() .query(function( err, data ) { console.log( err, data ) }) // return all attributes including non-projected ( LSI only ) DynamoDB .table('messages') .select( DynamoDB.ALL ) .where('to').eq('user1@test.com') .order_by('starredIndex') .descending() .query(function( err, data ) { console.log( err, data ) }) // NOTE: specifying non-projected fields in select() will: // * cost you extra reads on a LSI index // * not be returned on a GSI index

Query filtering

// A filter lets you apply conditions to the data after query // Only the items that meet your conditions are returned // All the conditions must evaluate to true ( conditions are ANDed together ) // You can not apply filter on HASH or RANGE key // Comparison operators: // The ones supported for RANGE key: // eq(), le() , lt() , ge() , gt() , begins_with() , between(a,b) // Plus: // ne(), defined(), undefined() // Unsupported yet ( for type SET ): // contains(), not_contains(), in() // // WARNING: defined() and undefined() are aliases for DynamoDB's NULL and NOT_NULL // they refer to the presence of an attribute and has nothing to do with it's value // so .having('attribute').null() differs from .having('attribute').eq( null ) // also .having('attribute').not_null() differs from .having('attribute').ne( null ) DynamoDB .table('messages') .where('to').eq('user1@test.com') .having('one_attribute').between(100,200) .having('object.attribute').eq(true) .having('deleted').undefined() // or .null() .having('last_login').defined() // or .not_null() .having('string').begins_with('substring') .having('string').contains('substring') .having('string_set').contains('one') .having('number').between(0,2) .having('attribute').in([3,4,'a']) .having('fridge.shelf[1].cookies').not_contains('sugar') .query(function( err, data ) { console.log( err, data ) })

Query continue from last item

// query a table until the end of results :) (function recursive_call( $lastKey ) { DynamoDB .table('messages') .where('to').eq('user1@test.com') .resume($lastKey) .query(function( err, data ) { // handle error, process data ... if (this.LastEvaluatedKey === null) { // reached end, do a callback() maybe return } var $this = this setTimeout(function() { recursive_call($this.LastEvaluatedKey) },1000) }) })(null)

Full table scan

// optionally you can limit the returned attributes with .select() // and the number of results with .limit() DynamoDB .table('messages') .select('from','subject','object.attribute','string_set[0]','array[1]') .having('somkey').eq('somevalue') .limit(10) .scan(function( err, data ) { console.log( err, data ) }) // continous scan until end of table (function recursive_call( $lastKey ) { DynamoDB .table('messages') .resume($lastKey) .scan(function( err, data ) { // handle error, process data ... if (this.LastEvaluatedKey === null) { // reached end, do a callback() maybe return } var $this = this setTimeout(function() { recursive_call($this.LastEvaluatedKey) },1000) }) })(null)

GSI scan

DynamoDB .table('messages') .index('GSI_Index_Name') .scan(function( err, data ) { console.log( err, data ) })

Consumed Capacity

As of version 0.1.51, aws-dynamodb returns TOTAL consumed capacity by default

DynamoDB .table($tableName) .operation(parameters, function callback() { console.log(this.ConsumedCapacity) }) // you can override it using DynamoDB .table($tableName) .return_consumed_capacity('INDEXES') // 'TOTAL' / 'INDEXES' / NONE .operation(parameters, function (err,data) { console.log(this.ConsumedCapacity) })

DynamoDB data types supported by aws-dynamodb

Please note binary data types BS is not supported yet.

Binary data type B is supported only as of 0.1.62

DynamoDB .table($tableName) .insert({ // String string1: 'string', string2: DynamoDB.S('string'), // Number number1: 1, number2: DynamoDB.N(1), // Boolean bool1: true, // Null null: null, // Buffer (B) buff: new Buffer('test'), // Array (L) array1: [1,[],true,{}], array2: DynamoDB.L([1,[],true,{}]), // Array of unique numbers numberSet (NS) number_set: DynamoDB.NS([111,222,333]), // Array or unique strings stringSet (SS) string_set: DynamoDB.SS(['aaa','bbb','ccc']), // Object key-value Map (M) object: { prop1: 1, prop2: '2', prop3: true, prop4: null, prop5: {}, prop6: [], prop7: new Buffer('test') }, })

Operations on supported data types

DynamoDB .table($tableName) .where(..) .update({ // delete an iteam of any data type any_item: DynamoDB.del(), // increment a number (N), use minus (-) to decrement number1: DynamoDB.add(), number2: DynamoDB.add(5), number3: DynamoDB.add(DynamoDB.N(5)), // add elements to an Array (L) array1: DynamoDB.add([1,[],{},null,'string']), array2: DynamoDB.add(DynamoDB.L([1,[],{},null,'string'])), // removing elements from an Array is not supported by AWS // adding properties to an Object (M) is not supported // deleting properties from an Object (M) is not supported // adding elements to a stringSet (SS) string_set: DynamoDB.add(DynamoDB.SS(['aaa','bbb'])), // removing elements from a stringSet (SS) string_set: DynamoDB.del(DynamoDB.SS(['aaa','bbb'])), // adding elements to a numberSet (NS) number_set: DynamoDB.add(DynamoDB.NS([111,222])), // removing elements from a numberSet (NS) number_set: DynamoDB.del(DynamoDB.NS([111,222])), })

Deprecated

To be removed in aws-dynamodb@0.2

DynamoDB .table('users') .where('email').eq('test@test.com') .increment({ login_count: 1 }, function( err, data ) { console.log( err, data ) })
// Deprecated ways to deleting an attribute DynamoDB .table('messages') .where('to').eq('user1@test.com') .where('date').eq( 1375538399 ) .update({ seen: undefined, subject: undefined }, function(err) {}) DynamoDB .table('messages') .where('to').eq('user1@test.com') .where('date').eq( 1375538399 ) .delete(['seen','subject'], function( err, data ) { console.log( err, data ) })

Fork me on GitHub