Run Bun Run!

Building an AWS CDK Template with Bun

·

6 min read

Run Bun Run!

On a Sunday morning, as you walk towards the bakery, the aroma of freshly baked buns fills the air. You can't resist taking a bite of the hot bun fresh from the oven as soon as you get it.

Now, it’s most likely that you burn yourself or you are smart and let it cool down a bit.

Bun is so cool that I also got hyped. However, I questioned myself:

  • How can I use it in my current or future projects?

  • How can I deploy it?

  • Do I have to rewrite a lot or even everything?

In this blog post, we will discover the usage of Bun by creating an AWS CDK template using Bun tooling.

What is Bun?

The NodeJS was established long ago but had no unified tooling and an ever-changing ecosystem. Bun wants to become the all-in-one JavaScript toolkit, including runtime, package manager, test runner, and bundler. It is built from scratch with three primary design goals: fast startup, fast running performance, and cohesive DX.

What does it mean “Runtime”?

It means how your computer interprets your script. Each programming language has its Syntax and grammar; you can run a program using their language^1. For example, if you write a Python script, you run it with python myscript.py . The machine uses Python as runtime to interpret it. The file extension allows humans to understand that this is a Python file or editors to do color highlighting, but it’s unnecessary for the machine. The same applies to NodeJS and Bun. You can execute the script by running node myscript.js and bun myscript.js. Notice that Bun can run Javascript.

More interesting is what Syntax Bun supports. - A bun of (pun intended ✊) including Typescript, JSX, and JSON².

What if we want to deploy a function to any Cloud provider such as Cloudflare or AWS? None of them support these for now (28. September 2023).

Remember, we need to run bun myscript.ts . Luckily, on AWS Lambda, we could create a custom runtime.

That means we don’t need to transpile the Typescript code to ESM or CJS. Currently, only Deno Deploy can run your Typescript function out of the box. However, in order to keep the code small, we still need some sort of bundling. Luckily, Bun is also a bundler 😉

What is a Bundler?

A bundler is a tool that puts your code into a single file. When you develop a function, you may split your code into multiple files and use third-party packages. So, instead of shipping the whole node_modules- folder or all the libraries, a bundler only uses the correct code.

We do this to optimize JavaScript files by reducing their size and number, which can significantly improve website performance, resulting in better user experience and increased engagement.

Furthermore, Node-Server cannot understand Typescript, so we use a bundler to transpile and bundle.

The famous bundler is Webpack, but esbuild is the fastest one… Well, until Bun 🍔

Bunnyfied AWS CDK project 🐰

As a cloud engineer, I am working a lot with AWS CDK, a tool for infrastructure on AWS. Usually, I develop my AWS Resources in SST ([here are my thoughts on SST](here my thoughts on SST) and not in Projen ([here are my thoughts on Projen](here my thoughts on SST).

AWS CDK

AWS CDK is an open-source software development framework developed by Amazon Web Services (AWS) that allows developers to define and provision cloud infrastructure resources using familiar programming languages.

Their CLI bootstraps an npm project. I used that to Bunyfied it.

bunx cdk init app --language typescript

For fun, I decided to test which package manager is the fastest for creating an AWS CDK project. It appears that bunx is faster than npx and pnpx. However, this was not a proper benchmarking test.

bunx-benchmark.gif

On this attempt bunx runs the fastest but is not really impressive.

bun test and bun install

When I first did it, I removed jest and package-lock.json because I wanted to try out the native Bun Test, and I used Bun’s package manager to install packages.

Testing

Bun comes with its own testing suite, and it gives you nice hints in order to make this test pass 😇

And I have to say the --watch mode is really fast. vitest is also fast and reruns only failed tests, which makes it, in the end, faster (here when using Monorepo).

bun-test-bench.gif

However, Mocking didn't work as expected. I attempted to spy on a function to manipulate a result but was unsuccessful. Their mocks provided were also unusable when trying to mock out the functions of packages. The documentation was not helpful either. As a result, I had to abandon bun test and used vitest instead.

Package Manager

Bun is clearly the fastest, and this time, you can really feel it 🤩

bun-package-bench.gif

When I installed Lambda Powertools, Bun became significantly faster. It turns out that I had already installed some packages, but still, Bun was faster than pnpm. ![bun-sinlge-package-bench 1.gif](jolo-dev-blog-images.s3.amazonaws.com/bun-s.. 1.gif)

Workspaces

I created a Monorepo because Bun supports Workspaces).

Deploying a Bun Runtime

Bun has its own package for creating a Lambda Layer.

I created a BunFunLayerStack to deploy a Lambda Layer. That layer is customizable and could be consumed by another account, or you could even make it public.

BunFun Construct

Then, I created a Construct BunFun which creates a Function using the Lambda Layer.

It tries to read Arn, resulting from the BunFunLayerStack but can be overwritten when passing props.bunLayer in case you bring your own Layer.

By default, the respective target will be used. If it's a Typescript or Javascript file, then node it will be the target. But then you could use the NodeJsFunction- Construct of CDK. However, when you need the Bun there is a bunConfig- property that also opens up all the Bun configurations from their bundler API.

I configured that it is optional, but when you use it, make sure you add a target. Sometimes I love Typescript 🤩

bun-fun-autocomplete.gif

Generally, I put three examples in the BunFunStack.

Run it

I put a couple of Lambdas in the packages/functions to get started and I realized that the Lambdas using that runtime must always return a new Response. Otherwise, the Lambda will not execute successfully. But that doesn't mean you cannot use it for handling SQS.

BunFunStack and BunFunLayerStack can be deployed separately. In this repo, I put them into an BunOvenStack.

Simply run bun deploy:all to deploy the BunOvenStack and run bun deploy:only to deploy only the BunFunStack. The bun deploy:only is also parameterized and could run another stack e.g. STACK=BunFunLayerStack bun deploy:only.

Conclusion

In this blog post, I used Bun's tooling to create an AWS CDK project. It includes a Layer for deploying a Bun Runtime and a Construct for creating a BunFunction.

Why should we care? Well, Bun would eliminate the fact of either using CommonJS or EcmaScript Modules (ESM) because most of the developers (me included) simply don't care. With the Bun runtime, you would run your Typescript files without transpiling them. Furthermore, Bun is fast. This blog does not cover benchmarking the Lambdas with a Bun runtime as there are already Benchmarks for Bun’s performance[^4](%5Bhttps://medium.com/@mitchellkossoris/serve..).

Unfortunately, bun test was not usable, and I use vitest instead. Moreover, the Lambda Runtime requires you always return new Response when using the bun target. This could be confusing when using Lambda for a different purpose then using an API Gateway.

With my Bunnyfied AWS CDK template, you could already start deploying Lambda functions using Bun Runtime, so give it a shoot 😉 To try it out, use the following command bun create github.com/jolo-dev/bunnyfied-aws-cdk

P.S.: It's faster than the npx cdk init app. P.S.S: Feedback and Contributions are welcome!