Run Bun Run! Building an AWS CDK Template with Bun
Tags: aws, typescript, bunjs, lambda
Published: 2023-09-28 08:45:28

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[^2].

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](https://deno.com/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,](https://webpack.js.org/) but [esbuild](https://esbuild.github.io/) is the fastest one… Well, until Bun 🍔
## Bunnyfied AWS CDK project 🐰
As a cloud engineer, I am working a lot with [AWS CDK](https://docs.aws.amazon.com/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](https://medium.com/@jolodev/sst-24fd4e2f4d7c)) and not in Projen ([here are my thoughts on Projen]([here my thoughts on SST](https://medium.com/@jolodev/sst-24fd4e2f4d7c)).
### 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](https://jolo-dev-blog-images.s3.amazonaws.com/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](https://bun.sh/docs/cli/test), and I used [Bun’s package manager](https://bun.sh/docs/cli/install) to install packages.
#### Testing
Bun comes with its own testing suite, and it gives you nice hints in order to make this test pass 😇

![Bun Test Hint](https://assets.vrite.io/646ce5fc07ef9ba4530fd23b/J8Iq7x4CL49UgjB3aDqTb.png)

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 Benchmarking](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/giqm7iq43ll59qejg3u2.gif)

However, Mocking didn't work as expected. I attempted to spy on a function to manipulate a result but was unsuccessful. Their [mocks](https://bun.sh/docs/test/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](https://jolo-dev-blog-images.s3.amazonaws.com/bun-package-bench.gif)

When I installed [Lambda Powertools](https://docs.powertools.aws.dev/lambda/typescript/latest/), 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](https://jolo-dev-blog-images.s3.amazonaws.com/bun-sinlge-package-bench 1.gif)
### Workspaces
I created a Monorepo because Bun supports [Workspaces](https://bun.sh/docs/install/workspaces)).
### Deploying a Bun Runtime
Bun has its own package for creating a [Lambda Layer](https://github.com/oven-sh/bun/tree/main/packages/bun-lambda).

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 `Fun`ction 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](https://bun.sh/docs/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](https://jolo-dev-blog-images.s3.amazonaws.com/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[^3].

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

```sh
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!

[^1]: [https://www.techtarget.com/searchsoftwarequality/definition/runtime](https://www.techtarget.com/searchsoftwarequality/definition/runtime)
[^2]: [https://bun.sh/docs/runtime/loaders](https://bun.sh/docs/runtime/loaders)
[^3]: [https://medium.com/@mitchellkossoris/serverless-bun-vs-node-benchmarking-on-aws-lambda-ecd4fe7c2fc2](https://medium.com/@mitchellkossoris/serverless-bun-vs-node-benchmarking-on-aws-lambda-ecd4fe7c2fc2)