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
}
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"
}
}
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
}
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
}
Image by giovanni gargiulo from Pixabay
Top comments (0)