DEV Community ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป

DEV Community ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป is a community of 963,274 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for Porting Curveball to Bun
Evert Pot
Evert Pot

Posted on • Originally published at evertpot.com

Porting Curveball to Bun

Bun is the hot new server-side Javascript runtime, in the same category as Node and Deno. Bun uses the JavascriptCore engine from Webkit, unlike Node and Deno which use V8. A big selling point is that
it's coming out faster in a many benchmarks, however the things I'm personally excited about is some of it's quality of life features:

  • It parses Typescript and JSX by default (but doesn't type check), which means there's no need for a separate 'dist' directory, or a separate tool like ts-node.
  • It loads .env files by default.
  • It's compatible with NPM, package.json, and many built-in Node modules.

I also like that it's 'Hello world HTTP server' is as simple as writing this file:

// http.ts
export default {
  port: 3000,
  fetch(request: Request): Promise<Response> {
    return new Response("Hello world!");
  },
};
Enter fullscreen mode Exit fullscreen mode

And then running it with:

bun run http.ts
Enter fullscreen mode Exit fullscreen mode

Bun will recognize that an object with a fetch function was default-exported, and start a server on port 3000. As you can see here, this uses the standard Request and Response objects you use in a browser, and can use async/await.

These are all things that didn't exist when Node and Express were first created, but seem like pretty good ideas for something built today. I don't think using Request and Response are good for more complex use-cases (streaming
responses, 1xx responses, trailers, upgrading to other protocols, getting tcp connection metadata like remoteAddr are some that come to mind), because these objects are designed for clients first.

But in many cases people are just building simple endpoints, and for that it's great.

Bun supports a ton of the standard Node modules, but it's also missing some such as support for server-side websockets and the node http/https/https packages, which for now makes it incompatible with popular frameworks like Express.

Porting Curveball

Curveball is a Typescript micro-framework weโ€™ve been developing since mid-2018 as a modern replacement for Express and Koa. A key difference between
Curveball and these two frameworks is that it fully abstracts and encapsulates the core 'Request' and 'Response' objects Node provides.

This made it very easy to create a lambda integration in the past; instead of mapping to Node's Request and Response types, All I needed was simple mapping function for Lambdas idea of what a request and response looks like.

To get Express to run on AWS Lambda the Node http stack needs to be emulated, or a full-blown HTTP/TCP server needs to be started and proxied to. Each of these workarounds require a ton of code from libraries like serverless-express.

So with Bun up and coming, either the same work would need to be done to emulate Node's APIs, or Bun would would need to add full compability for the Node http module (which is eventually coming).

But because Curveball abstractions, it was relatively easy to get up and running. Most of the work was moving the Node-specific code into a new package.

Here's the Curveball 'hello world' on Bun:

import { Application } from '@curveball/kernel';

const app = new Application();

// Add all your middlewares here! This should look familiar to Koa users.
app.use( ctx => {
  ctx.response.body = {msg: 'hello world!'}; 
});

export default {
  port: 3000,
  fetch: app.fetch.bind(app)
};
Enter fullscreen mode Exit fullscreen mode

It's still a bit experimental, but the following middlewares are tested:

And because JSX also just works in Bun, it's relatively easy to use it to generate HTML:

import { Application } from '@curveball/kernel';
import reactMw from '@curveball/react';
import React from 'react';

const app = new Application();

app.use(reactMw());

app.use( ctx => {

  ctx.response.type = 'text/html; charset=utf-8';
  ctx.response.body = <html>
    <body>
      <div>
        <h1>Hello world!</h1>
      </div>
    </body>
  </html>;

});

export default {
  port: 3000,
  fetch: app.fetch.bind(app)
};
Enter fullscreen mode Exit fullscreen mode

This is not a full-blown hydration/server-rendering solution, but JSX has replaced template engines like EJS and Handlebars for us at Bad Gateway. JSX lets you do the same things, but you get the advantage of type checking, it's harder to create XSS bugs and full Javascript access.

Final thoughts on Bun

Compared to Deno, It was considerably easier to port Curveball to Bun.
Deno was a much greater challenge, due to it having such a radically different idea about packages and module loading. This was over a year ago, so it's worth giving a shot again.

So, I'm curious where all of this goes! Perhaps Bun is the future, or perhaps we'll see Node get parity with Bun and making it obsolete. Either way competition is good.

I feel Bun has a better chance than Deno, because it delivers many of its advantages and features while mostly staying inside with the Node ecosystem.

Although as it turns out Deno also has changed their tune and also made it a new goal to be compatible. I can't help wondering if this was inspired by Bun's recent success as well.

Top comments (2)

Collapse
 
devarshishimpi profile image
Devarshi Shimpi

Interesting Article to try out with Bun. Great work!!

Collapse
 
sindouk profile image
Sindou Konรฉ

Add to the discussion

Need a better mental model for async/await?

Check out this classic DEV post on the subject.

โญ๏ธ๐ŸŽ€ JavaScript Visualized: Promises & Async/Await

async await