AWS Lambda & Bun: A Perfect Match for Serverless Bliss

AWS Lambda & Bun: A Perfect Match for Serverless Bliss


Serverless computing has revolutionized the way we build and deploy applications, offering scalability and cost-efficiency like never before. AWS Lambda, Amazon's serverless compute service, has played a pivotal role in this paradigm shift. Lambda allows developers to run code in response to events without the need to manage servers.

However, Lambda's default runtime environment supports only a limited set of programming languages. If your favorite language isn't among them, you might find yourself facing a roadblock. That's where custom runtimes and extensions come into play. In this blog post, we'll explore how you can use a custom runtime layer to run Bun on AWS Lambda.

Title: Unleashing the Power of Bun with AWS Lambda Extensions

Introduction

Serverless computing has revolutionized the way we build and deploy applications, offering scalability and cost-efficiency like never before. AWS Lambda, Amazon's serverless compute service, has played a pivotal role in this paradigm shift. Lambda allows developers to run code in response to events without the need to manage servers.

However, Lambda's default runtime environment supports only a limited set of programming languages. If your favorite language isn't among them, you might find yourself facing a roadblock. That's where custom runtimes and extensions come into play. In this blog post, we'll explore how you can use a custom runtime layer to run Bun on AWS Lambda.

Bun:

Bun is a new JavaScript runtime built from scratch to serve the modern JavaScript ecosystem. It has three major design goals:

  • Speed. Bun starts fast and runs fast. It extends JavaScriptCore, the performance-minded JS engine built for Safari. As computing moves to the edge, this is critical.
  • Elegant APIs. Bun provides a minimal set of highly-optimimized APIs for performing common tasks, like starting an HTTP server and writing files.
  • Cohesive DX. Bun is a complete toolkit for building JavaScript apps, including a package manager, test runner, and bundler.

Bun is designed as a drop-in replacement for Node.js. It natively implements hundreds of Node.js and Web APIs, including fs, path, Buffer and more.

The goal of Bun is to run most of the world's server-side JavaScript and provide tools to improve performance, reduce complexity, and multiply developer productivity.

AWS Lambda Layer:

AWS Lambda Layers is a versatile feature that allows you to manage and reuse code, libraries, and dependencies across multiple Lambda functions. While I've covered the basics, here's a more in-depth exploration of Lambda Layers and how they can be used for runtime enhancements:

1. Code Isolation and Reusability:

Lambda Layers enable you to isolate your code and dependencies from your Lambda function's deployment package. This separation enhances code reusability, reduces duplication, and simplifies code maintenance.

2. Runtimes and Custom Runtimes:

Lambda Layers can contain custom runtime environments, which is a powerful capability. This allows you to run Lambda functions in languages and versions not natively supported by AWS Lambda. For instance, you can create a custom runtime layer for running operations written in Rust, Kotlin, or any language you choose.

3. Runtime Environment Customization:

You can use Lambda Layers to customize the runtime environment of your Lambda functions. For example, you can include environment variables, initialization code, or configurations specific to your application.

4. Shared Libraries and Dependencies:

Share common libraries and dependencies across multiple Lambda functions within an application. This reduces the size of individual deployment packages and simplifies updates when there are changes or security patches to libraries.


Running AWS Lambda Functions with Bun: A Step-by-Step Guide:

Step 1: Prerequisites

  • AWS account and AWS CLI configured
  • Node.js and NPM installed
  • Basic knowledge of JavaScript/TypeScript
  • Bun CLI Installed (https://bun.sh/)

Step 2: Deploy Bun Lambda Layer:

Bun team has provided the official documentation for the bun lambda. Please refer the link (https://github.com/oven-sh/bun/tree/main/packages/bun-lambda)

Clone this repository and run the publish-layer script to get started. Note: the publish-layer hand also builds the layer.

git clone [email protected]:oven-sh/bun.git
cd bun/packages/bun-lambda
bun install
bun run publish-layer        

bun run publish-layer has the following arguments:

--layer: The layer name. The default value is bun

--region: The region name, or "*" for all regions.

--public: If the layer should be public. The default value is false.

--arch: The architecture, either: "x64" or "aarch64". For lambda arm64, the architecture will be aarch64 and for x86_64 based lambda, the architecture will be x64

The full example will be:

bun run publish-layer -- \
  --arch aarch64 \
  --release latest \
  --region us-east-1        

This will be deploy arm64 lambda layer and after the deployment, you will get published lambda layer ARN. This ARN will be required for the Lambda Deployment for Lambda Function deployment.

Step 3: Create Lambda Function:

  1. Create a folder with the following command and navigate to the folder:

mkdir app && cd app        

  1. Create a Bun application with the following command:

bun init -y        

This will create a default Bun application.

  1. Update the index.ts file with the following code snippet:

export default {
	async fetch(request: Request): Promise<Response> {
		// ...
		return new Response("Hello from Lambda! via Bun Lambda Layer", {
			status: 200,
			headers: {
				"Content-Type": "text/plain",
			},
		});
	},
};        

This code exports an object with a single asynchronous function, fetch, typically used in the context of an AWS Lambda function. Let's break down what this code does:

  • Exporting a Default Object:export default {...}: This code exports an object as the default export of the module. The object contains a single property, fetch, which is an asynchronous function.
  • fetch Function:async fetch(request: Request): Promise<Response>: The fetch function is declared as an asynchronous function. It takes a single argument, request, of type Request. The fetch function is typically used to handle HTTP requests.
  • Returning a Response:return new Response("Hello from Lambda! via Bun Lambda Layer", {...}): This line creates and returns an HTTP response. It responds with a simple text message, "Hello from Lambda! via Bun Lambda Layer," and sets the response status code to 200 (indicating a successful response).The response object includes a headers field with the "Content-Type" header set to "text/plain," specifying that the response body is plain text.

To build the code, run the following command:

bun build --minify --splitting --outdir=dist ./index.ts        

The command is used to build a TypeScript file (index.ts) using "bun" with specific options and output configuration. Here's a breakdown of the command:

  • bun: This is the command-line interface (CLI) for the "bun" tool, which is a JavaScript and TypeScript bundler that can be used to bundle and optimize code for deployment.
  • build: This is the subcommand for the "bun" CLI, indicating that you want to initiate a build process.
  • --minify: This option instructs "bun" to minify the generated output code. Minification is the process of removing unnecessary characters (such as whitespace and comments) from the code to reduce its size, making it more efficient for production use.
  • --splitting: This option suggests that you want to enable code splitting. Code splitting is a technique that allows you to split your code into smaller chunks or modules. This can improve the performance of your application by reducing the initial download size for users.
  • --outdir=dist: This option specifies the output directory where the bundled and minified code should be placed. In this case, the code will be placed in a directory named "dist."
  • ./index.ts: This is the entry point for the bundling process. The "./index.ts" file is the main TypeScript file that you want to bundle. The bundler will start from this file and resolve dependencies as needed.

Step 4: Deployment using AWS CDK:

To get started with the AWS Cloud Development Kit (CDK) for deployment, follow these short notes:

  • Deploy folder:Create a deploy folder at the root location and navigate to the deploy folder using command:

mkdir deploy && cd deploy        

  • Installation:Install Node.js if not already installed.Install the AWS CDK globally using npm:

npm install -g aws-cdk        

  • Initialize Your CDK App:Create a new directory for your CDK app and navigate to it in your terminal.Initialize a new CDK app using the following command:

cdk init app --language=typescript        

This will initialized the CDK project. Navigate to lib/deploy-stack.ts file and update the following code snippet:Imports:

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Runtime, Function, Code, LayerVersion, FunctionUrlAuthType, Architecture } from 'aws-cdk-lib/aws-lambda';
import * as path from 'path';
import { CfnOutput } from 'aws-cdk-lib';        

  1. The code begins by importing necessary AWS CDK constructs and modules. These include constructs for Lambda functions, Lambda layers, stack properties, and outputs.
  2. DeployStack Class:

export class DeployStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    // ... rest of the code
  }
}
        

The DeployStack class extends the cdk.Stack class, indicating that it represents an AWS CloudFormation stack.

  1. Lambda Layer Configuration:

const lambdaLayer = LayerVersion.fromLayerVersionArn(this, 'BunLayer', 'arn:aws:lambda:us-east-1:XXXXXX:layer:bun:1');        

This section defines a Lambda Layer named BunLayer by creating an instance of LayerVersion and specifying its ARN. The ARN points to an existing Lambda Layer.

4. Lambda Function Configuration:

const lambdaFunction = new Function(this, 'BunFunction', {
  runtime: Runtime.PROVIDED_AL2,
  code: Code.fromAsset(path.join(__dirname, '../../app/dist')),
  handler: 'index.fetch',
  layers: [lambdaLayer],
  functionName: 'bun',
  architecture: Architecture.ARM_64,
});        

This section configures the Lambda function named BunFunction.It uses the Runtime.PROVIDED_AL2, which suggests that the runtime is provided by the layer.The function's code is sourced from the specified directory using Code.fromAsset.The handler indicates that the function is located in the index file and the fetch function is the entry point.The Lambda function is associated with the lambdaLayer.It is given the name bun.The architecture is set to Architecture.ARM_64, which indicates the architecture of the Lambda function.

  1. Lambda Function URL Configuration:

const lambdaUrl = lambdaFunction.addFunctionUrl({
  authType: FunctionUrlAuthType.NONE,
});        

This section configures the URL for the Lambda function.The authType is set to FunctionUrlAuthType.NONE, indicating that no authentication is required to access the function URL.

  1. CDK Output:

new CfnOutput(this, 'FunctionUrl', { value: lambdaUrl.url });        

This code defines a CloudFormation output that displays the URL of the Lambda function. The output is named 'FunctionUrl'.

Full Code:

import * as cdk from 'aws-cdk-lib';
import {Construct} from 'constructs';
import {Runtime, Function, Code, LayerVersion, FunctionUrlAuthType, Architecture} from "aws-cdk-lib/aws-lambda";
import * as path from "path";
import {CfnOutput} from "aws-cdk-lib";

// import * as sqs from 'aws-cdk-lib/aws-sqs';

export class DeployStack extends cdk.Stack {
	constructor(scope: Construct, id: string, props?: cdk.StackProps) {
		super(scope, id, props);
		
		// The code that defines your stack goes here
		
		const lambdaLayer = LayerVersion.fromLayerVersionArn(this, 'BunLayer', 'arn:aws:lambda:us-east-1:XXXXXX:layer:bun:1')
		const lambdaFunction = new Function(this, 'BunFunction', {
			runtime: Runtime.PROVIDED_AL2,
			code: Code.fromAsset(path.join(__dirname, '../../app/dist')),
			handler: 'index.fetch', // index is the name of the file and fetch is the name of the function
			layers: [lambdaLayer],
			functionName: 'bun',
			architecture: Architecture.ARM_64, // ARM_64 is the architecture of the lambda function and this will changed based on the bun image architecture
		});
		
		const lambdaUrl = lambdaFunction.addFunctionUrl({
			authType: FunctionUrlAuthType.NONE,
		});
		
		new CfnOutput(this, 'FunctionUrl ', { value: lambdaUrl.url });
	}
}        

  1. Synthesize and Deploy:Run the following command to synthesize your CDK app into AWS CloudFormation templates:

cdk synth && cdk deploy        

This will deploy the stack and at the end you will get the function URL

When you open the URL in the browser, you will see the message:

Hello from Lambda! via Bun Lambda Layer        

Conclusion

In conclusion, serverless computing has ushered in a transformative era in application development, offering unprecedented scalability and cost-efficiency. AWS Lambda, Amazon's serverless compute service, has been at the forefront of this shift by enabling developers to execute code in response to events without the need to manage servers.

However, one limitation of AWS Lambda's default runtime environment is its support for only a select set of programming languages. If your preferred language is not on the list, it can be a roadblock to your development efforts. This is where custom runtimes and extensions come into play, providing a solution to this challenge.

In this post, we've explored how you can harness the power of Bun, a modern JavaScript runtime, within AWS Lambda using custom runtime layers. Bun is designed for speed, elegant APIs, and a cohesive developer experience. It serves as a drop-in replacement for Node.js, implementing a wide range of Node.js and Web APIs.

We've also delved into AWS Lambda Layers, a versatile feature for managing and reusing code, libraries, and dependencies across multiple Lambda functions. Layers offer several advantages, including code isolation, runtime customization, and shared libraries.

To put this into practice, we've outlined a step-by-step guide for running AWS Lambda functions with Bun:

  1. Prerequisites: Ensure you have an AWS account, AWS CLI configured, Node.js and NPM installed, and basic knowledge of JavaScript/TypeScript.
  2. Deploy Bun Lambda Layer: Follow the provided instructions to deploy the Bun Lambda Layer, including cloning the repository, running the publish-layer script, and specifying deployment options such as architecture and region.
  3. Create Lambda Function: Create a folder for your Lambda application, initialize a Bun application, and update the index.ts file with your code logic. This code snippet exports an asynchronous function called fetch that handles HTTP requests.
  4. Build the Code: Use the Bun CLI to build your TypeScript code with specific options such as minification and code splitting. This step prepares your code for deployment.
  5. Deployment using AWS CDK: To deploy your Bun-powered Lambda function, set up an AWS CDK project. The code defines an AWS CDK stack called DeployStack, configures the Lambda function with the Bun Layer, and generates a CloudFormation output containing the function's URL.

By following these steps, you can unlock the full potential of Bun within AWS Lambda, leveraging custom runtimes and layers to expand your serverless development capabilities. This approach allows you to use your preferred language and runtime environment while benefiting from the scalability and serverless advantages provided by AWS Lambda. Happy serverless coding!

Source Code:

You can find the source code in the provided repository: aws-lambda-bun-layer-example.

You can follow me on LinkedIn to get the latest updates on Serverless and AI.

#Serverless #AWSLambda #Bun #ServerlessDevelopment #CustomRuntimes #LambdaLayers


Andrew Hammond

Engineer, Maker, Entrepreneur

1 年

But can the CDK app portion be done with Bun instead of node?

Sean Knowles

Software Engineer (AWS) at Tellimer

1 年

How does this improve on node specifically when using this with lambda, are there cold start improvements, request response time improvements, cost optimisations?

要查看或添加评论,请登录

Durgaprasad Budhwani的更多文章