How to run Bun on Lambda
What is Bun and why you should use it? Also everything is more fun with AWS Lambda, right?
Bun is fast JavaScript runtime/package manager/bundler/test runner/all-in-one toolkit as it states on its homepage.
As soon as I've heard about Bun I've been hooked. If you for any reason cannot use the runtime itself I still recommend to use Bun's package manager. Needless to say it is super quick and you can read why in the offical Bun blog post.
I like the package manager not only for its speed but also for monorepo features like for instance catalog. I don't care it was inspired by pnpm because it just works.
Nevertheless my by far most favourite thing about Bun is its bundler capabilities. No more hustle with configuring the React build pipeline, no more fight with HMR! Bun has it all. Out of box. You just need to use it.
Yes it is not perfect and I find myself to restarting the server bunch of times but just the idea of the simplicity and potential that it will work one day as expected makes me feel really good about this.
How it works? You have your usual index.html file with script tag <script type="module" src="./frontend.tsx"></script> (yes, you import .tsx as Bun understand TypeScript out of box) and you just import the HTML and run the server like this:
// dev.tsx
import index from "@app/web/src/index.html";
import { serve } from "bun";
const server = serve({
routes: {
// Serve index.html for all unmatched routes.
"/*": index,
},
development: process.env.NODE_ENV !== "production" && {
// Enable browser hot reloading in development
hmr: true,
// Echo console logs from the browser to the server
console: true,
},
});
console.log(`๐ Server running at ${server.url}`);
Then you just run bun --hot dev.tsx and you are good to go.
Do you want to run HTTPS in local development? Don't worry, just add devcert to the mix like this:
import index from "@app/web/src/index.html";
import { serve } from "bun";
import { certificateFor } from "devcert";
const server = serve({
routes: {
// Serve index.html for all unmatched routes.
"/*": index,
},
development: process.env.NODE_ENV !== "production" && {
// Enable browser hot reloading in development
hmr: true,
// Echo console logs from the browser to the server
console: true,
},
tls: await certificateFor("your-project.dev"),
hostname: "your-project.dev",
});
console.log(`๐ Server running at ${server.url}`);
Want to run some API along your React application? Easy!
import index from "@app/web/src/index.html";
import api from "@app/api/api";
import auth from "@app/auth/api";
import { serve } from "bun";
import { certificateFor } from "devcert";
const server = serve({
routes: {
// Serve index.html for all unmatched routes.
"/*": index,
"/api/*": (request: Request) => {
request.headers.set(
"x-amzn-authorizer",
fakeAuthorizerHeader(),
);
return api.fetch(request);
},
"/auth/*": auth.fetch,
},
development: process.env.NODE_ENV !== "production" && {
// Enable browser hot reloading in development
hmr: true,
// Echo console logs from the browser to the server
console: true,
},
tls: await certificateFor("your-project.dev"),
hostname: "your-project.dev",
});
console.log(`๐ Server running at ${server.url}`);
Ok, hold your horses. An explanation is needed.
Bun supports fetch Reqeust/Response Web API so in order to export your server endpoint you just need to export function which takse in Request and returns Response. Like this:
export default {
fetch: async (request: Request): Promise<Response> => {
// do your logic here
return new Response(JSON.stringify({ok: true}), {
status: 200,
headers: {
"Content-Type": "application/json",
},
})
}
}
Even better, since Bun also comes with official custom Lambda runtime from now on if you decide to you can run exactly the same code as you run locally in your Lambda functions.
This means that you don't need no special toolchain for faking Lambda environment on your local machine. If you go fully Serverless you can locally connect to remote DynamoDB table and run your code with Bun exactly like you will run it in Lambda.
Yes, in the previous example I am using fakeAuthorizerHeader() function which just fakes some data which when deployed to AWS comes from HTTP API Lambda Authorizer. So from time to time you will need to fake some parts of the AWS infrastructure but I think that the gains here are worth it. You should have staging environment deployed somewhere anyway so any infrastructure changes should be tested in the AWS environment before they hit production.
There is slight problem with the official Bun runtime - it seems that it is very low priority and you need to wait long periods of time for fixing stupid bugs or introducing new features. That's why at BeeSolve we've forked the runtime, added our changes so the runtime works with both HttpEventV1 and HttpEventV2 properly and we've also provided CDK construct for easy BunLambdaLayer and BunFunction deployment. The Bun layer is prebuilt with version v1.3.2 and we plan to keep it updated.
You can install the runtime with npm or any package manager you like:
npm i @beesolve/lambda-bun-runtime
# or give it a try with bun
bun i @beesolve/lambda-bun-runtime
The code is available for review at GitHub.
If you have any questions regarding running Bun in Lambda or about local development of fully Serverless AWS native web application please feel free to contact me.
