

# Node.js and DAX
<a name="DAX.client.run-application-nodejs-3"></a>

# Default client configuration for Node.js
<a name="DAX-client-config-JS"></a>

When configuring the DAX JavaScript SDK client, you can customize various parameters to optimize performance, connection handling, and error resilience. The following table outlines the default configuration settings that control how your client interacts with the DAX cluster, including timeout values, retry mechanisms, credential management, and health monitoring options. For more information, see [DynamoDBClient Operations](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/dynamodb/).


**DAX JS SDK client defaults**  

| Parameter | Type | Description | 
| --- | --- | --- | 
|  `region` optional  |  `string`  |  The AWS Region to use for the DAX client (example - 'us-east-1'). This is a required parameter if not provided through the environment variable.  | 
|  `endpoint` required  |  `string`  | The endpoint of the Cluster to which the SDK connects. Examples: Non-encrypted – dax-cluster-name.region.amazonaws.com Encrypted – daxs://my-cluster.l6fzcv.dax-clusters.us-east-1.amazonaws.com  | 
|  `requestTimeout` default 6000 ms  |  `number`  | This defines the maximum time the client will wait for a response from DAX.  | 
|  `writeRetries` default 1  |  `number`  | The number of retries to attempt for write requests that fail.  | 
|  `readRetries` default 1  |  `number`  | The number of retries to attempt for read requests that fail.  | 
|  `maxRetries` default 1  |  `number`  | The maximum number of retries to attempt on failed requests. If readRetries/writeRetries are set, then the configuration set in readRetries and writeRetries take priority over maxRetries.  | 
|  `connectTimeout` default 10000 ms  |  `number`  | The timeout (in milliseconds) for establishing a connection to any of the cluster nodes.  | 
|  `maxConcurrentConnections` default 100  |  `number`  | Limits the total number of concurrent connections that a client instance can create per node in a DAX cluster.  | 
|  `maxRetryDelay` default 7000 ms  |  `number`  | When the DAX server indicates recover is needed by setting `waitForRecoveryBeforeRetrying` flag to true, the client will pause before retry attempts. During these recovery periods, the `maxRetryDelay` parameter determines the maximum waiting time between retries. This recovery-specific configuration only applies when the DAX server is in recovery mode. For all other scenarios, retry behavior follows one of two patterns: either an exponential delay based on the retry count (governed by `writeRetries`, `readRetries`, or `maxRetries` parameters), or an immediate retry depending on the exception type.  | 
|  `credentials` optional  |  `[AwsCredentialIdentity](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-credential-providers/)` \$1 `[AwsCredentialIdentityProvider](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-credential-providers/)`  |  The AWS credentials to use for authenticating requests. This can be provided as an AwsCredentialIdentity or an AwsCredentialIdentityProvider. If not provided, the AWS SDK will automatically use the default credentials provider chain. Example: `\$1 accessKeyId: 'AKIA...', secretAccessKey: '...', sessionToken: '...' \$1` \$1 @default Uses default AWS credentials provider chain.  | 
|  `healthCheckInterval` default 5000 ms  |  `number`  | The interval (in milliseconds) between cluster health checks. A lower interval will check more frequently.  | 
|  `healthCheckTimeout` default 1000 ms  |  `number`  | The timeout (in milliseconds) for the health check to complete.  | 
|  `skipHostnameVerification` default false  |  `boolean`  |  Skip hostname verification of TLS connections. This has no impact on un-encrypted clusters. The default is to perform hostname verification, setting this to True will skip verification. Be sure you understand the implication of turning it off, which is the inability to authenticate the cluster that you are connecting to.   | 
|  `unhealthyConsecutiveErrorCount` default 5  |  `number`  | Sets the number of consecutive errors required to signal node unhealthy within health check interval.  | 
|  `clusterUpdateInterval` default 4000 ms  |  `number`  | Returns the interval between polling of cluster members for membership changes.  | 
|  `clusterUpdateThreshold` default 125  |  `number`  | Returns the threshold below which the cluster will not be polled for membership changes.  | 
|  `credentailProvider` optional \$1 default null  |  `[AwsCredentialIdentityProvider](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-credential-providers/)`  | User Defined Provider for AWS credentials used to authenticate requests to DAX.  | 


**Pagination configuration for DaxDocument**  

| Name | Type | Detail | 
| --- | --- | --- | 
|  `client`  |  DaxDocument  |  Instance of DaxDocument type.  | 
|  `pageSize`  |  number  |  Determines the number of items per page.  | 
|  `startingToken` Optional  |  any  |  LastEvaluatedKey from previous response can be used for subsequent requests.  | 

For usage of pagination, see [TryDax.js](DAX.client.tutorial-TryDax.md).

# Migrating to DAX Node.js SDK V3
<a name="DAX.client.run-application-nodejs-3-migrating"></a>

This migration guide will help you transition your existing DAX Node.js applications. The new SDK requires Node.js 18 or higher and introduces several important changes in how you'll structure your DynamoDB Accelerator code. This guide will walk you through the key differences, including syntax changes, new import methods, and updated asynchronous programming patterns.

## V2 Node.js DAX usage
<a name="DAX.client.run-application-nodejs-3-migrating-V2-usage"></a>

```
const AmazonDaxClient = require('amazon-dax-client');
const AWS = require('aws-sdk');

var region = "us-west-2";

AWS.config.update({
  region: region,
});

var client = new AWS.DynamoDB.DocumentClient();

if (process.argv.length > 2) {
  var dax = new AmazonDaxClient({
    endpoints: [process.argv[2]],
    region: region,
  });
  client = new AWS.DynamoDB.DocumentClient({ service: dax });
}

// Make Get Call using Dax
var params = {
    TableName: 'TryDaxTable',
    pk: 1,
    sk: 1
}
client.get(params, function (err, data) {
    if (err) {
        console.error(
            "Unable to read item. Error JSON:",
            JSON.stringify(err, null, 2)
          );
    } else {
        console.log(data);
    }
});
```

## V3 Node.js DAX usage
<a name="DAX.client.run-application-nodejs-3-migrating-V3-dax-usage"></a>

For Using DAX Node.js V3 Node version 18 or above is the preferred version. To move to Node 18, use the following:

```
import { DaxDocument } from '@amazon-dax-sdk/lib-dax';
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';

let client: DynamoDBDocument | DaxDocument = DynamoDBDocument.from(
  new DynamoDBClient({ region: 'us-west-2' })
);

if (process.argv.length > 2) {
  client = new DaxDocument({
    endpoints: [process.argv[2]],
    region: 'us-west-2',
  });
}

const params = {
  TableName: 'TryDaxTable',
  Key: { pk: 1, sk: 1 },
};

try {
  const results = await client.get(params);
  console.log(results);
} catch (err) {
  console.error(err);
}
```

The DAX SDK for Node.js v3.x is compatible with [AWS SDK for Node.js v3.x](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/introduction/). The DAX SDK for Node.js v3.x supports the use of [aggregated](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/introduction/#high-level-concepts) clients. Please note that DAX doesn't support the creation of bare-bones clients. For more details on unsupported features, see [Features not in parity with AWS SDK V3](#DAX.client.run-application-nodejs-3-not-in-parity).

Follow these steps to run the Node.js sample application on your Amazon EC2 instance.

**To run the Node.js sample for DAX**

1. Set up Node.js on your Amazon EC2 instance, as follows:

   1. Install node version manager (`nvm`).

      ```
      curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
      ```

   1. Use nvm to install Node.js.

      ```
      nvm install 18
      ```

   1. Use nvm to use Node 18

      ```
      nvm use 18
      ```

   1. Test that Node.js is installed and running correctly.

      ```
      node -e "console.log('Running Node.js ' + process.version)"
      ```

      This should display the following message.

      `Running Node.js v18.x.x`

1. Install the DaxDocument Node.js client using the node package manager (`npm`).

   ```
   npm install @amazon-dax-sdk/lib-dax
   ```

## TryDax sample code
<a name="DAX.client.run-application-nodejs-3-TryDax-sample-code"></a>

To evaluate the performance benefits of DynamoDB Accelerator (DAX), follow these steps to run a sample test that compares read operation times between standard DynamoDB and a DAX cluster.

1. After you've set up your workspace and installed the `lib-dax` as a dependency, copy [TryDax.js](DAX.client.tutorial-TryDax.md) into your project.

1. Run the program against your DAX cluster. To determine the endpoint for your DAX cluster, choose one of the following: 
   +  **Using the DynamoDB console** — Choose your DAX cluster. The cluster endpoint is shown on the console, as in the following example.

     ```
     dax://my-cluster.l6fzcv.dax-clusters.us-east-1.amazonaws.com
     ```
   + **Using the AWS CLI** — Enter the following command.

     ```
     aws dax describe-clusters --query "Clusters[*].ClusterDiscoveryEndpoint"
     ```

     The cluster endpoint is shown in the output, as in the following example.

     ```
     {
         "Address": "my-cluster.l6fzcv.dax-clusters.us-east-1.amazonaws.com",
         "Port": 8111,
         "URL": "dax://my-cluster.l6fzcv.dax-clusters.us-east-1.amazonaws.com"
     }
     ```

1. Now run the program by specifying the cluster endpoint as a command line parameter.

   ```
   node TryDax.js dax://my-cluster.l6fzcv.dax-clusters.us-east-1.amazonaws.com
   ```

   You should see output similar to the following:

   ```
   Attempting to create table; please wait...
   Successfully created table. Table status: ACTIVE
   Writing data to the table...
   Writing 20 items for partition key:  1
   Writing 20 items for partition key:  2
   Writing 20 items for partition key:  3
   ...
   Running GetItem Test
           Total time: 153555.10 µs - Avg time: 383.89 µs
           Total time: 44679.96 µs - Avg time: 111.70 µs
           Total time: 36885.86 µs - Avg time: 92.21 µs
           Total time: 32467.25 µs - Avg time: 81.17 µs
           Total time: 32202.60 µs - Avg time: 80.51 µs
   Running Query Test
           Total time: 14869.25 µs - Avg time: 2973.85 µs
           Total time: 3036.31 µs - Avg time: 607.26 µs
           Total time: 2468.92 µs - Avg time: 493.78 µs
           Total time: 2062.53 µs - Avg time: 412.51 µs
           Total time: 2178.22 µs - Avg time: 435.64 µs
   Running Scan Test
           Total time: 2395.88 µs - Avg time: 479.18 µs
           Total time: 2207.16 µs - Avg time: 441.43 µs
           Total time: 2443.14 µs - Avg time: 488.63 µs
           Total time: 2038.24 µs - Avg time: 407.65 µs
           Total time: 1972.17 µs - Avg time: 394.43 µs
   Running Pagination Test
   Scan Pagination
   [
     { pk: 1, sk: 1, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 2, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 3, someData: 'XXXXXXXXXX' }
   ]
   [
     { pk: 1, sk: 4, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 5, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 6, someData: 'XXXXXXXXXX' }
   ]
   ...
   Query Pagination
   [
     { pk: 1, sk: 1, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 2, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 3, someData: 'XXXXXXXXXX' }
   ]
   [
     { pk: 1, sk: 4, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 5, someData: 'XXXXXXXXXX' },
     { pk: 1, sk: 6, someData: 'XXXXXXXXXX' }
   ]
   ...
   Attempting to delete table; please wait...
   Successfully deleted table.
   ```

   Take note of the timing information. The number of microseconds required for the `GetItem`,`Query`,`Scan` tests.

1. In this case, you ran the programs against the DAX cluster. Now, you'll run the program again, this time against DynamoDB.

1. Now run the program again, but this time, without the cluster endpoint URL as a command line parameter.

   ```
   node TryDax.js
   ```

   Look at the output and take note of the timing information. The elapsed times for `GetItem`, `Query`, and `Scan` should be significantly lower with DAX as compared to DynamoDB.

## Features not in parity with AWS SDK V3
<a name="DAX.client.run-application-nodejs-3-not-in-parity"></a>
+ [Bare-bones](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/introduction/#high-level-concepts) clients – Dax Node.js V3 doesn’t support bare-bones clients. 

  ```
  const dynamoDBClient = new DynamoDBClient({ region: 'us-west-2' });
  const regularParams = {
      TableName: 'TryDaxTable',
      Key: {
          pk: 1,
          sk: 1
      }
  };
  // The DynamoDB client supports the send operation.
  const dynamoResult = await dynamoDBClient.send(new GetCommand(regularParams));
  
  // However, the DaxDocument client does not support the send operation.
  const daxClient = new DaxDocument({
      endpoints: ['your-dax-endpoint'],
      region: 'us-west-2',
  });
  
  const params = {
      TableName: 'TryDaxTable',
      Key: {
          pk: 1,
          sk: 1
      }
  };
  
  // This will throw an error - send operation is not supported for DAX. Please refer to documentation.
  const result = await daxClient.send(new GetCommand(params));
  console.log(result);
  ```
+ [Middleware Stack](https://aws.amazon.com/blogs/developer/middleware-stack-modular-aws-sdk-js/) – Dax Node.js V3 doesn’t support using Middleware functions.

  ```
  const dynamoDBClient = new DynamoDBClient({ region: 'us-west-2' });
  // The DynamoDB client supports the middlewareStack.
  dynamoDBClient.middlewareStack.add(
    (next, context) =>> async (args) => {
      console.log("Before operation:", args);
      const result = await next(args);
      console.log("After operation:", result);
      return result;
    },
    {
      step: "initialize", // or "build", "finalizeRequest", "deserialize"
      name: "loggingMiddleware",
    }
  );
  
  // However, the DaxDocument client does not support the middlewareStack.
  const daxClient = new DaxDocument({
      endpoints: ['your-dax-endpoint'],
      region: 'us-west-2',
  });
  
  // This will throw an error - custom middleware and middlewareStacks are not supported for DAX. Please refer to documentation.
  daxClient.middlewareStack.add(
    (next, context) => async (args) => {
      console.log("Before operation:", args);
      const result = await next(args);
      console.log("After operation:", result);
      return result;
    },
    {
      step: "initialize", // or "build", "finalizeRequest", "deserialize"
      name: "loggingMiddleware",
    }
  );
  ```

# TryDax.js
<a name="DAX.client.tutorial-TryDax"></a>

```
import { DynamoDB, waitUntilTableExists, waitUntilTableNotExists } from "@aws-sdk/client-dynamodb";
import { DaxDocument, daxPaginateScan, daxPaginateQuery } from "@amazon-dax-sdk/lib-dax";
import { DynamoDBDocument, paginateQuery, paginateScan } from "@aws-sdk/lib-dynamodb";

const region = "us-east-1";
const tableName = "TryDaxTable";

// Determine the client (DynamoDB or DAX)
let client = DynamoDBDocument.from(new DynamoDB({ region }));
if (process.argv.length > 2) {
  client = new DaxDocument({ region, endpoint: process.argv[2] });
}

// Function to create table
async function createTable() {
  const dynamodb = new DynamoDB({ region });
  const params = {
    TableName: tableName,
    KeySchema: [
      { AttributeName: "pk", KeyType: "HASH" },
      { AttributeName: "sk", KeyType: "RANGE" },
    ],
    AttributeDefinitions: [
      { AttributeName: "pk", AttributeType: "N" },
      { AttributeName: "sk", AttributeType: "N" },
    ],
    ProvisionedThroughput: { ReadCapacityUnits: 25, WriteCapacityUnits: 25 },
  };

  try {
    console.log("Attempting to create table; please wait...");
    await dynamodb.createTable(params);
    await waitUntilTableExists({ client: dynamodb, maxWaitTime: 30 }, { TableName: tableName });
    console.log("Successfully created table. Table status: ACTIVE");
  } catch (err) {
    console.error("Error in table creation:", err);
  }
}

// Function to insert data
async function writeData() {
  console.log("Writing data to the table...");
  const someData = "X".repeat(10);
  for (let ipk = 1; ipk <= 20; ipk++) {
    console.log("Writing 20 items for partition key: ", ipk)
    for (let isk = 1; isk <= 20; isk++) {
      try {
        await client.put({ TableName: tableName, Item: { pk: ipk, sk: isk, someData } });
      } catch (err) {
        console.error("Error inserting data:", err);
      }
    }
  }
}

// Function to test GetItem
async function getItemTest() {
  console.log("Running GetItem Test");
  for (let i = 0; i < 5; i++) {
    const startTime = performance.now();
    const promises = [];
    for (let ipk = 1; ipk <= 20; ipk++) {
      for (let isk = 1; isk <= 20; isk++) {
        promises.push(client.get({ TableName: tableName, Key: { pk: ipk, sk: isk } }));
      }
    }
    await Promise.all(promises);
    const endTime = performance.now();
    const iterTime = (endTime - startTime) * 1000;
    const totalTime = iterTime.toFixed(2).padStart(3, ' ');
    const avgTime = (iterTime / 400).toFixed(2).padStart(3, ' ');
    console.log(`\tTotal time: ${totalTime} \u00B5s - Avg time: ${avgTime} \u00B5s`);
  }
}

// Function to test Query
async function queryTest() {
  console.log("Running Query Test");
  for (let i = 0; i < 5; i++) {
    const startTime = performance.now();
    const promises = [];
    for (let pk = 1; pk <= 5; pk++) {
      const params = {
        TableName: tableName,
        KeyConditionExpression: "pk = :pkval and sk between :skval1 and :skval2",
        ExpressionAttributeValues: { ":pkval": pk, ":skval1": 1, ":skval2": 2 },
      };
      promises.push(client.query(params));
    }
    await Promise.all(promises);
    const endTime = performance.now();
    const iterTime = (endTime - startTime) * 1000;
    const totalTime = iterTime.toFixed(2).padStart(3, ' ');
    const avgTime = (iterTime / 5).toFixed(2).padStart(3, ' ');
    console.log(`\tTotal time: ${totalTime} \u00B5s - Avg time: ${avgTime} \u00B5s`);
  }
}

// Function to test Scan
async function scanTest() {
  console.log("Running Scan Test");
  for (let i = 0; i < 5; i++) {
    const startTime = performance.now();
    const promises = [];
    for (let pk = 1; pk <= 5; pk++) {
      const params = {
        TableName: tableName,
        FilterExpression: "pk = :pkval and sk between :skval1 and :skval2",
        ExpressionAttributeValues: { ":pkval": pk, ":skval1": 1, ":skval2": 2 },
      };
      promises.push(client.scan(params));
    }
    await Promise.all(promises);
    const endTime = performance.now();
    const iterTime = (endTime - startTime) * 1000;
    const totalTime = iterTime.toFixed(2).padStart(3, ' ');
    const avgTime = (iterTime / 5).toFixed(2).padStart(3, ' ');
    console.log(`\tTotal time: ${totalTime} \u00B5s - Avg time: ${avgTime} \u00B5s`);
  }
}

// Function to test Pagination
async function paginationTest() {
  console.log("Running Pagination Test");
  console.log("Scan Pagination");
  const scanParams = { TableName: tableName };
  const paginator = process.argv.length > 2 ? daxPaginateScan : paginateScan;
  for await (const page of paginator({ client, pageSize: 3 }, scanParams)) {
    console.log(page.Items);
  }

  console.log("Query Pagination");
  const queryParams = {
    TableName: tableName,
    KeyConditionExpression: "pk = :pkval and sk between :skval1 and :skval2",
    ExpressionAttributeValues: { ":pkval": 1, ":skval1": 1, ":skval2": 10 },
  };
  const queryPaginator = process.argv.length > 2 ? daxPaginateQuery : paginateQuery;
  for await (const page of queryPaginator({ client, pageSize: 3 }, queryParams)) {
    console.log(page.Items);
  }
}

// Function to delete the table
async function deleteTable() {
  const dynamodb = new DynamoDB({ region });
  console.log("Attempting to delete table; please wait...")
  try {
    await dynamodb.deleteTable({ TableName: tableName });
    await waitUntilTableNotExists({ client: dynamodb, maxWaitTime: 30 }, { TableName: tableName });
    console.log("Successfully deleted table.");
  } catch (err) {
    console.error("Error deleting table:", err);
  }
}

// Execute functions sequentially
(async function () {
  await createTable();
  await writeData();
  await getItemTest();
  await queryTest();
  await scanTest();
  await paginationTest();
  await deleteTable();
})();
```