DEV Community

Cover image for ESModule in AWS Lambda
Prabusah
Prabusah

Posted on • Edited on

6 3

ESModule in AWS Lambda

Why module system

  • Organise source code (into multiple files based on functionality).
  • Include code from sdk, frameworks and packages from other users into our application.

NodeJS default module system
It is CommonJS (Here's CommonJS way of adding AWS SDK const AWS = require('aws-sdk');).

  • By default AWS Lambda adds aws-sdk Version2 (CommonJS).
  • So we do not need to npm install aws-sdk dependency and upload the archive file to AWS.

Lets take an example Lambda code that uses CommonJS

  • Environment variable named 'secret' is encrypted using Customer Managed KMS Key.
  • Global variable 'decrypted' behaves as below:
    • Cold Start: 'decrypted' value is null... so "if condition" in lambda passes and calls fetchDecryptedValue.
    • Warm Start: 'decrypted' is not null... so "if condition" NOT passes... reuses already decrypted value.

CommonJS example

const AWS = require('aws-sdk');
//Brings in entire AWS SDK that contains all services clients.

const functionName = process.env.AWS_LAMBDA_FUNCTION_NAME;
const encrypted = process.env['secret'];
//Environment variable 'secret' is encrypted using Customer Managed Key.

let decrypted;
//Cold Start: 'decrypted' value is null.
//Warm Start: 'decrypted' value is not null - contains decrypted value.

exports.handler = async (event) => {
    if (!decrypted) {
        //matches only during Cold Start
        await fetchDecryptedValue();
    }
    processEvent(event, decrypted);
};

let fetchDecryptedValue = async function() {
    const kms = new AWS.KMS();
    try {
        const req = {
            CiphertextBlob: Buffer.from(encrypted, 'base64'),
            EncryptionContext: { LambdaFunctionName: functionName},
        };
        const data = await kms.decrypt(req).promise();
        decrypted = data.Plaintext.toString('ascii');
    } catch (err) {
        console.log('Decrypt error:', err);
        throw err;
    }
}

let processEvent = function(event, decrypted) {
    // use decrypted credential here
}
Enter fullscreen mode Exit fullscreen mode

ESModule example

  • JavaScript is evolving and current standard is ECMAScript module(ESModule) system.
  • ESModule is supported by modern browsers and NodeJS runtime starting version 14.

Pre-requisite to make ESModule work in Lambda

  • package.json should contain type as 'module'.
{
    "name": "blog-esmodule",
    "type": "module",
    "description": "Example for esmodule usage in AWS Lambda.",
    "version": "1.0",
    "main": "index.js",
    "dependencies": {
        "@aws-sdk/client-kms": "^3.76.0"
    }
}
Enter fullscreen mode Exit fullscreen mode
  • npm install @aws-sdk/client-kms
  • This is AWS SDK Version3 - supports ESModule system
  • Archive and upload the code & dependencies to AWS Lambda. (Step by step demonstration will be in a separate post).
//ESModule example
import { KMSClient, DecryptCommand } from "@aws-sdk/client-kms";
// Imports only the kms service client. Reduced code size. Improved cold start time

const functionName = process.env.AWS_LAMBDA_FUNCTION_NAME;
const encrypted = process.env['secret'];
let decrypted;
//during Cold/Warm start behaves same as CommonJS lambda code

export const handler = async (event) => {
    if (!decrypted) {
        await fetchDecryptedValue();
    }
    processEvent(event, decrypted);
};

let fetchDecryptedValue = async function() {
    const client = new KMSClient();

    try {
        const req = {
            CiphertextBlob: Buffer.from(encrypted, 'base64'),
            EncryptionContext: { LambdaFunctionName: functionName },
        };
        const command = new DecryptCommand(req);
        const data = await client.send(command);
        decrypted = Buffer.from(data.Plaintext).toString();
    } catch (err) {
        console.log('Decrypt error:', err);
        throw err;
    }
}

let processEvent = function(event, decrypted) {
    // use decrypted credential here
}

Enter fullscreen mode Exit fullscreen mode

Benefits of ESModule

  • Import only the required code.
  • Improved cold start time (since package size is reduced).
  • Top level await.

Top-level await in AWS Lambda

  • Use NodeJS runtime version >= 14
  • Allows us to 'await' outside of handler. Below example shows ESModule Lambda using Top-level await feature.
//Top-level await ESModuleJS
import { KMSClient, DecryptCommand } from "@aws-sdk/client-kms"; // ES Modules import
const functionName = process.env.AWS_LAMBDA_FUNCTION_NAME;
const encrypted = process.env['secret'];
let decrypted = await fetchDecryptedValue();
//This is not possible in CommonJS

export const handler = async (event) => {
    //Clean code - No need of condition checking on 'decrypted' global variable.
    processEvent(event, decrypted);
};

async function fetchDecryptedValue() {
    let decryptedValue = '';
    const client = new KMSClient();

    try {
        const req = {
            CiphertextBlob: Buffer.from(encrypted, 'base64'),
            EncryptionContext: { LambdaFunctionName: functionName },
        };
        const command = new DecryptCommand(req);
        const data = await client.send(command);
        decryptedValue = Buffer.from(data.Plaintext).toString();

    } catch (err) {
        console.log('Decrypt error:', err);
        throw err;
    }
    return decryptedValue;
}

let processEvent = function(event, decrypted) {
    // use decrypted credential here
}
Enter fullscreen mode Exit fullscreen mode

Image by giovanni gargiulo from Pixabay

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more