Elysia - Building Scalable Web Services with Bun

Elysia - Building Scalable Web Services with Bun

Published:

Bun is faster than NodeJS. But what about creating a real-world application server with it? There is no routing, plugin and middleware provided by Bun itself. Elysia is built to provide those missing pieces.

Cover image

Which problems does Elysia solve for us?

When rebuilding my blog, I decided to write a simple service that mirrors the content that I manage in Notion.

After I set up my project, one of the first questions to solve was: “How do I best build the REST API of my service with Bun?”

Of course, I could have implemented the endpoints by using the plain serve -API of Bun. But it quickly became ridiculously complex when I created my first few routes.

From the NodeJS ecosystem, I was used to fastify . It provides everything you need to build a solid, scalable REST-API. Before I defaulted to using fastify again for this project — It works on Bun as well — I took a look at what alternatives we have with Bun.

I stumbled over a benchmark repository that quickly lead me to elysia .

Elysia is an HTTP server library built for the Bun runtime. It is an alternative to express, fastify and others.

It provides basic features that you would expect, like routing and plugins. But it provides even more than this out-of-the-box, like schema validation via @sinclair/typebox , cookie handling and type safe API access. On top of this, there are plenty of plugins that you can use.

Let’s take a look at the core properties of Elysia.

Fast

Elysia claims to be 21 times faster than express. The underlying benchmark is created by the main developer of Elysia, so it should be treated with care.

Apart from this, it is only a slim layer around Bun, so we should be safe to expect reasonable performance.

Simple

The API of Elysia is a bit similar to express, but it is even simpler.

It felt very straightforward to use.

We can create a server like this:

import Elysia from "elysia";

const app = new Elysia()
  .get("/", () => "Hello World!")
  .listen(9001);

And creating plugins is very convenient as well.

import Elysia from "elysia";

const plugin = new Elysia().get("/a", () => "Hello World!");

new Elysia().use(plugin).listen(9001);

A plugin is nothing else than a new Elysia instance. This way, you can turn any plugin into a server instance by just calling .listen() on it. If you want to configure it, you can simply wrap the plugin in a function, like this:

import Elysia from "elysia";

const plugin = (prefix: string) =>
  new Elysia({ prefix }).get("/", () => "Hello World!");

new Elysia().use(plugin("/a")).listen(9001);

Validation and Typesafety

Elysia comes with @sinclair/typebox to validate your requests. If you define a request schema with typebox for path params, query params or request body of an endpoint, incoming requests will be validated and in TypeScript, you have those request properties typed with the according types. The same is true for responses.

import { Elysia, t } from "elysia";

const app = new Elysia()
  .post(
    "/a/b/:x",
    ({ params, body, query }) => ({
      concatenated: `${params.x} - ${query.y} - ${body.z}`,
    }),
    {
      params: t.Object({ x: t.Numeric() }),
      query: t.Object({ y: t.String() }),
      body: t.Object({ z: t.String() }),
      response: t.Object({ concatenated: t.String() }),
    },
  )
  .listen(9001);

export type App = typeof app;

In this example, we will get a 400 HTTP status response, if our request or response does not match the schema.

You can even reuse those types on the client to ensure that you call your REST APIs correctly. For this, you can install the @elysiajs/eden package and run requests like this:

import { edenTreaty } from "@elysiajs/eden";
import type { App } from "./elysia-typesafe-server";

const api = edenTreaty<App>("http://localhost:9001");

const res = await api.a.b[123].post({
  $query: { y: "foo" },
  z: "Hello",
});

console.log(
  res.status,
  res.error ? res.error.value : res.data.concatenated,
);

Is it a good choice to bet on Elysia?

Elysia has been released since December 2022. So it was around before Bun 1.0 was released and had some time to mature. It also gained some traction since the official Bun release happened.

I think there is still some risk involved in building on Elysia. It works well for me, but I only built a very basic REST API by now, so this may not be very representative.

That said, the solid performance and high development productivity that comes with it may outweigh this risk. I also wouldn’t be surprised if it really takes off, the more Bun is adopted by the community.

For my part, I’ll keep using it.

Never stop learning and Happy Coding!