Scaling Up: Hot-Reloading Multiple TypeScript Lambdas in LocalStack

darasimiajewole

Darasimi-Ajewole

Posted on January 26, 2024

Scaling Up: Hot-Reloading Multiple TypeScript Lambdas in LocalStack

In a recent consultancy project, I was tasked with creating a mock environment for an asynchronous webservice using LocalStack, with the primary aim of minimizing the feedback loop associated with integrating our systems. This involved using AWS CDK to provision multiple TypeScript runtime lambdas in LocalStack and esbuild to build the functions.

On completing the mock environment, I decided to go the extra mile by attempting to enable hot reloading for the lambdas, a feature that proved more challenging than expected, as outlined in the official LocalStack guide.

After navigating through moments of frustration, fueled by a few cups of Yorkshire tea, I eventually uncovered the solution to hot-reloading multiple TypeScript lambdas in LocalStack. Below, I have outlined guide on how to achieve this for others navigating similar challenges.

Before diving into this guide, it's recommended to familiarize yourself with the official hot-reloading Localstack guide for a single Lambda function.

The following steps provide a summary of the official guide for hot-reloading TypeScript Lambda functions in LocalStack:

  1. The Build Step:
    package.json

    "scripts": {
            "build": "esbuild index.ts \
                       --bundle --minify --sourcemap \
                       --platform=node --target=es2020 \
                       --outfile=dist/index.js --watch"
        },
    


    Executing the above build script with npm run build uses esbuild to bundle and minify the TypeScript code into a single JavaScript file, stored in the dist folder.

  2. Deploying to LocalStack

awslocal lambda create-function \
   --function-name hello-world \
   --runtime "nodejs16.x" \
   --role arn:aws:iam::123456789012:role/lambda-ex \
   --code S3Bucket="hot-reload",S3Key="$(PWD)/dist" \
   --handler index.handler
Enter fullscreen mode Exit fullscreen mode

If followed correctly, these steps should enable hot-reloading for a single Lambda function after every code change.

Hot-Reloading Multiple Lambdas

To extend hot-reloading to multiple Lambdas, a key modification is needed at the build step:

"scripts": {
        "build": "esbuild lambdas/src/*.ts 
                    --bundle --minify --sourcemap \
                    --platform=node --target=es2020 \
                    --outdir=lambdas/build --watch"
    },
Enter fullscreen mode Exit fullscreen mode

This command individually transpiles, bundles, and minifies each Lambda entry file in the lambdas/src folder, placing the output in the lambdas/build directory.

Note: The lambdas/src folder should match the directory where your Lambdas reside. Other bundlers, not just esbuild, can be used for the build step.

Proceed to deploy each Lambda to Localstack using the awslocal lambda create-function command, and they should hot-reload with every code change.

Important: When deploying from within a container, ensure that the S3key is a path on the host, as Localstack maps directories from the host to the Lambda containers.


Bonus Information: AWS Cloud Development Kit (CDK) Configuration for Hot-Reloaded Lambda.

import { NodejsFunction, NodejsFunctionProps } from 'aws-cdk-lib/aws-lambda-nodejs';
import { Architecture, Code, Function, Runtime } from "aws-cdk-lib/aws-lambda";
import { Bucket } from "aws-cdk-lib/aws-s3";
import { Construct } from "constructs";
import { join, basename } from "path";


const STAGE = process.env.STAGE ?? "local";
const LAMBDA_MOUNT_CWD = join(__dirname, "lambdas/build")

function ApplicationFunction(
  scope: Construct,
  id: string,
  props: NodejsFunctionProps
) {
  if (STAGE === "local") {
    return LocalFunction(scope, id, props);
  }
  return new NodejsFunction(scope, id, props);
}

function LocalFunction(
  scope: Construct,
  id: string,
  props: NodejsFunctionProps
) {
  const hotReloadBucket = Bucket.fromBucketName(
    scope,
    `HotReloadingBucket-${id}`,
    "hot-reload"
  );

  if (!props.entry) throw new Error('Entry point is required');

  const fileName = basename(props.entry, ".ts");
  const handler = props.handler ?? "handler";
  const runtime = props.runtime || Runtime.NODEJS_18_X;

  return new Function(scope, id, {
    ...props,
    code: Code.fromBucket(hotReloadBucket, join(__dirname, "lambdas/build")),
    runtime,
    handler: `${fileName}.${handler}`,
  });
}

Enter fullscreen mode Exit fullscreen mode

Bootstrap and deploy to LocalStack by running:

cdklocal bootstrap && cdklocal deploy --require-approval never
Enter fullscreen mode Exit fullscreen mode

For a complete working example of hot-reloading multiple TypeScript Lambdas, refer to this example CRUD application with five hot-reloaded Lambdas.

May your Lambdas always hot-reload!

💖 💪 🙅 🚩
darasimiajewole
Darasimi-Ajewole

Posted on January 26, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related