Skip to content

Web Development · Backend

ElysiaJS in 2026: End-to-End Type Safety for Bun APIs Without the Ceremony

ElysiaJS gives Bun apps fast routing, request validation, and full type inference between server and client — without code generation. Here's how it works and when to use it.

Anurag Verma

Anurag Verma

5 min read

ElysiaJS in 2026: End-to-End Type Safety for Bun APIs Without the Ceremony

Sponsored

Share

Express is 15 years old. Fastify is better, but its TypeScript support still feels bolted on. If you’re building APIs on Bun and want type safety without stitching together three libraries and a code generator, ElysiaJS is the option worth knowing.

It’s designed around Bun’s runtime, uses TypeBox for validation instead of Zod (which keeps startup times low), and ships a client library called Eden Treaty that gives you full type inference from the server to whatever is calling it. No schema generation step. No GraphQL. Just TypeScript types that follow the contract from one end to the other.

What ElysiaJS Actually Does

At the core, it’s an HTTP router with schema validation built in. You define routes the same way you define types:

import { Elysia, t } from "elysia";

const app = new Elysia()
  .get("/health", () => ({ status: "ok" }))
  .post("/users", ({ body }) => {
    return { id: crypto.randomUUID(), email: body.email };
  }, {
    body: t.Object({
      email: t.String({ format: "email" }),
      name: t.String({ minLength: 1 }),
    }),
    response: t.Object({
      id: t.String(),
      email: t.String(),
    }),
  })
  .listen(3000);

export type App = typeof app;

The t object is TypeBox, which compiles JSON Schema validators at startup rather than running Zod’s parser on every request. In benchmarks, this matters: ElysiaJS consistently places near the top of Node/Bun HTTP framework comparisons. Real applications won’t hit the theoretical ceiling, but the per-request overhead is genuinely lower than Express or NestJS.

Eden Treaty: The Client Side

The App type exported from the server is the contract. On the client:

import { treaty } from "@elysiajs/eden";
import type { App } from "../server";

const api = treaty<App>("http://localhost:3000");

// Fully typed: api.users.post knows what body it accepts
// and what response to expect
const { data, error } = await api.users.post({
  email: "user@example.com",
  name: "Alice",
});

if (error) {
  console.error(error.message);
} else {
  console.log(data.id); // string — TypeScript knows this
}

This is the part that makes ElysiaJS interesting. You’re not running tRPC (which requires a separate router setup), not running GraphQL (which requires a schema and resolvers), and not generating API clients from an OpenAPI spec. The server’s type definition is the client’s type definition.

The main limitation: Eden Treaty works well for TypeScript-to-TypeScript calls. If your clients are mobile apps or third-party integrators, you’ll still want to emit an OpenAPI spec. ElysiaJS has a Swagger plugin for that.

Plugin System

import { Elysia } from "elysia";
import { cors } from "@elysiajs/cors";
import { swagger } from "@elysiajs/swagger";
import { bearer } from "@elysiajs/bearer";

const app = new Elysia()
  .use(cors({
    origin: ["https://app.example.com"],
    methods: ["GET", "POST", "PUT", "DELETE"],
  }))
  .use(swagger({
    documentation: {
      info: { title: "API", version: "1.0.0" },
    },
  }))
  .use(bearer())
  .guard({
    beforeHandle: ({ bearer, set }) => {
      if (!bearer) {
        set.status = 401;
        return "Unauthorized";
      }
    },
  }, (app) =>
    app
      .get("/me", ({ bearer }) => {
        return { token: bearer };
      })
  )
  .listen(3000);

The guard pattern groups routes under shared middleware. You apply beforeHandle hooks to a block of routes rather than adding middleware per-route or globally. This maps well to how real applications are structured: public routes, authenticated routes, admin routes.

Organizing a Real Application

For anything beyond a toy API, split routes across files:

// routes/users.ts
import { Elysia, t } from "elysia";
import { db } from "../db";

export const usersRouter = new Elysia({ prefix: "/users" })
  .get("/", async () => {
    return db.query.users.findMany();
  })
  .get("/:id", async ({ params, set }) => {
    const user = await db.query.users.findFirst({
      where: (users, { eq }) => eq(users.id, params.id),
    });
    if (!user) {
      set.status = 404;
      return { error: "Not found" };
    }
    return user;
  }, {
    params: t.Object({ id: t.String() }),
  });

// app.ts
import { Elysia } from "elysia";
import { usersRouter } from "./routes/users";

const app = new Elysia()
  .use(usersRouter)
  .listen(3000);

export type App = typeof app;

Eden Treaty understands the prefix, so api.users.get() routes correctly on the client side.

vs Hono

Hono is the other major contender in this space. Both run on Bun, Cloudflare Workers, Deno, and Node. The relevant differences:

Hono has broader runtime support and more documentation around edge deployments. Its RPC mode gives you similar type inference to Eden Treaty. ElysiaJS has the validation story better integrated — TypeBox schemas validate and type-infer in one step — and the guard pattern is cleaner for middleware organization.

For Cloudflare Workers deployments, Hono is the safer choice because ElysiaJS’s Workers support is still maturing. For a Bun server on your own infrastructure, both work well.

When to Use It

Good fit:

  • Full-stack TypeScript apps where the client is also TypeScript (Next.js frontend, React Native app)
  • Bun projects where startup time matters (short-lived containers, serverless)
  • Teams that want type safety without pulling in tRPC or setting up code generation
  • Internal tools and admin APIs where you control both ends of the connection

Less ideal:

  • APIs consumed by clients in other languages (document the Swagger output carefully)
  • Projects that need the ecosystem breadth of Express or Fastify
  • Teams not already using Bun (ElysiaJS runs on Node, but its advantages are clearest on Bun)

Getting Started

bun create elysia my-api
cd my-api
bun dev

The scaffolded project includes a basic Elysia app, TypeScript config, and a test file. The framework is stable enough for production use. The ecosystem is smaller than Express, but the core features — routing, validation, Eden Treaty, Swagger — cover what most API projects need without reaching for external packages.

Sponsored

Sponsored

Discussion

Join the conversation.

Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.

Sponsored