Streamlining Client Initialization and Configuration with AWS SDK v3 for JavaScript

Upgrade to AWS SDK v3 for efficient service-specific packages, smaller sizes, and tailored configurations. Simplify setup with centralized initialization and streamline client implementations.

Streamlining Client Initialization and Configuration with AWS SDK v3 for JavaScript
Source: https://docs.aws.amazon.com/images/sdk-for-javascript/v3/developer-guide/images/sdk-overview-v3.png

In our company's application backend, we are currently utilizing 3 AWS SDK clients. In AWS SDK v2, all the service clients were integrated into a single comprehensive package (approximately 40MB). However, with the new AWS SDK v3 implementation, services are distributed across distinct packages tailored to specific functionalities. This redesign results in significantly smaller package sizes, with an overhead of only about 3MB. However, it introduces a requirement for separate configurations for each of these services.

This characteristic can offer advantages, depending on your AWS setup. For example, it proves beneficial in scenarios where you need to configure each service with unique settings, such as distinct regions and credentials. On the flip side, if your services are located within the same region and you utilize identical IAM credentials, this approach might become unwieldy.
Read more:

In this blog post, I will demonstrate a straightforward method to centralize the initialization of all these clients in a single location.

Optimizing AWS Client Configuration and Implementation

The core idea revolves around establishing a centralized AWS initialization configuration file - such as aws.initClients.js. Within this file, all clients are imported and configured using a consistent application-wide setup. To enhance flexibility, the @aws-sdk/credential-providers module can be leveraged to source credentials from various origins. For this demonstration, we'll focus on utilizing credentials stored in environment variables. For more comprehensive insights into loading AWS credentials, you can refer to https://www.npmjs.com/package/@aws-sdk/credential-providers.

To handle specific AWS client implementations, a prudent approach is to create distinct implementation files. For each client, these files would import the preconfigured client settings from aws.initClients.js. Moreover, it's essential to incorporate all operation commands pertinent to the particular clients. An illustrative case involves importing the GetParameterCommand for interactions with the ssmClient. Let's delve into a exemplar client implementation scenario.

File Structure

Example structure:

example/path/aws
├── aws.initClients.js
├── aws.s3Client.js
├── aws.snsClient.js
└── aws.ssmClient.js

All configurations are located in the aws folder.

File: aws.initClients.js

File content of aws.initClients.js:

"use srict"
const { SNSClient } = require("@aws-sdk/client-sns");
const { S3Client } = require("@aws-sdk/client-s3");
const { SSMClient } = require("@aws-sdk/client-ssm");
const { fromEnv } = require("@aws-sdk/credential-providers");

// Set up global configuration for all clients
const globalConfig = {
  region: process.env.AWS_REGION ? process.env.AWS_REGION : 'eu-west-1',
  credentials: fromEnv()
};

// Create AWS service clients using the global configuration
const s3Client = new S3Client(globalConfig);
const snsClient = new SNSClient(globalConfig);
const ssmClient = new SSMClient(globalConfig);

module.exports = {
  s3Client,
  snsClient,
  ssmClient,
};

In essence, this code sets up a global configuration object with region and credentials, initializes AWS service clients for S3, SNS, and SSM using the global configuration, and exports these clients for use in other parts of the application. This approach centralizes the client initialization process and ensures consistent configuration across the application.

The fromEnv() function reads the credentials from the following environment variables if they are set:

  • AWS_ACCESS_KEY_ID - The access key for your AWS account.
  • AWS_SECRET_ACCESS_KEY - The secret key for your AWS account.
  • AWS_SESSION_TOKEN - The session key for your AWS account. This is only needed when you are using temporarycredentials.
  • AWS_CREDENTIAL_EXPIRATION - The expiration time of the credentials contained in the environment variables described above. This value must be in a format compatible with the ISO-8601 standard and is only needed when you are using temporary credentials.

File aws.s3Client.js

Example implementation of aws.s3Client.js:

"use strict";
const { s3Client } = require("./aws.initClients")
const {
  ListObjectsV2Command,
  PutObjectCommand,
  DeleteObjectCommand,
  GetObjectCommand,
} = require("@aws-sdk/client-s3");

/**
 * Lists objects in an S3 bucket.
 * @param {Object} bucketParams - Parameters for listing objects in the bucket.
 * @returns {Promise} A promise that resolves to the response of the list operation.
 */
const s3ClientListObjects = async (bucketParams) => {
  return await s3Client.send(new ListObjectsV2Command(bucketParams));
};

/**
 * Deletes an object from an S3 bucket.
 * @param {Object} bucketParams - Parameters for the target bucket.
 * @param {string} key - The key of the object to be deleted.
 * @returns {Promise} A promise that resolves to the response of the delete operation.
 */
const s3ClientDeleteObject = async (bucketParams, key) => {
  return await s3Client.send(new DeleteObjectCommand({
    Bucket: bucketParams.Bucket,
    Key: key
  }));
};

/**
* Retrieves an object from an S3 bucket.
* @param {Object} bucketParams - Parameters for the target bucket.
* @param {string} key - The key of the object to be retrieved.
* @returns {Promise} A promise that resolves to the response of the get operation.
*/
const s3ClientGetObject = async (bucketParams, key) => {
  return await s3Client.send(new GetObjectCommand({
    Bucket: bucketParams.Bucket,
    Key: key
  }));
};

/**
 * Uploads an object to an S3 bucket.
 * @param {Object} bucketParams - Parameters for the target bucket.
 * @param {string} key - The key under which to store the new object.
 * @param {ReadableStream} fileStream - Readable stream representing the content of the object.
 * @returns {Promise} A promise that resolves to the response of the put operation.
 */
const s3ClientPutObject = async (bucketParams, key, fileStream) => {
  return await s3Client.send(new PutObjectCommand({
    ...bucketParams,
    Key: key,
    Body: fileStream,
  }));
};

module.exports = {
  s3ClientListObjects,
  s3ClientDeleteObject,
  s3ClientGetObject,
  s3ClientPutObject,
};

File aws.ssmClient.js

Example implementation of aws.ssmClient.js:

"use strict";
const { ssmClient } = require("./aws.initClients")
const { GetParameterCommand } = require("@aws-sdk/client-ssm");

/**
 * Fetches the value of an SSM Parameter from AWS Parameter Store.
 * @param {string} parameterName - The name of the SSM Parameter.
 * @returns {Promise<string>} A promise that resolves to the value of the parameter.
 */
const ssmClientGetParameterValue = async (parameterName) => {
  const paramOptions = { Name: parameterName, WithDecryption: true };
  const ssmGetParameterCommand = new GetParameterCommand(paramOptions);

  const response = await ssmClient.send(ssmGetParameterCommand);
  return response.Parameter.Value;
};

/**
 * Fetches values for multiple SSM Parameters.
 * @param {Object} ssmParameters - An object containing parameter keys and their corresponding names.
 * @returns {Promise<Array>} A promise that resolves to an array of objects containing parameter keys and values.
 */
const ssmClientGetAllParameterValues = async (ssmParameters) => {
  const parametersToFetch = Object.entries(ssmParameters).map(async ([key, value]) => {
    const parameterValue = await ssmClientGetParameterValue(value);
    return { [key]: parameterValue };
  });

  const returnValues = await Promise.all(parametersToFetch);
  return returnValues;
};

module.exports = {
  ssmClientGetParameterValue,
  ssmClientGetAllParameterValues,
};

Conclusion

In summary, the shift from AWS SDK v2 to v3 introduces a more modular approach, reducing package sizes but necessitating individual configurations. This offers flexibility for diverse scenarios. By centralizing client initialization and utilizing distinct implementation files, developers can efficiently manage AWS interactions and adapt to specific requirements. The transition empowers applications with optimized configurations and streamlined client implementations.