DEV Community

Cover image for ESModule in AWS Lambda
Prabusah
Prabusah

Posted on • Edited on

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)